Merge "Show a toast for background activity starts but only abort the start if the feature is enabled (it's disabled by default)"
diff --git a/Android.bp b/Android.bp
index 286be82..a5cc89c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -49,6 +49,9 @@
"rs/java/**/*.java",
":framework-javastream-protos",
+ // TODO: Resolve circular library dependency and remove media1-srcs and mediasession2-srcs
+ ":media1-srcs",
+ ":mediasession2-srcs",
"core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl",
"core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl",
@@ -191,6 +194,10 @@
"core/java/android/hardware/input/IInputDevicesChangedListener.aidl",
"core/java/android/hardware/input/ITabletModeChangedListener.aidl",
"core/java/android/hardware/iris/IIrisService.aidl",
+ "core/java/android/hardware/location/IActivityRecognitionHardware.aidl",
+ "core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl",
+ "core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl",
+ "core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl",
"core/java/android/hardware/location/IGeofenceHardware.aidl",
"core/java/android/hardware/location/IGeofenceHardwareCallback.aidl",
"core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl",
@@ -207,7 +214,6 @@
"core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl",
"core/java/android/hardware/usb/IUsbManager.aidl",
"core/java/android/hardware/usb/IUsbSerialReader.aidl",
- "core/java/android/net/ICaptivePortal.aidl",
"core/java/android/net/IConnectivityManager.aidl",
"core/java/android/hardware/ISensorPrivacyListener.aidl",
"core/java/android/hardware/ISensorPrivacyManager.aidl",
@@ -468,14 +474,11 @@
"media/java/android/media/IAudioRoutesObserver.aidl",
"media/java/android/media/IAudioService.aidl",
"media/java/android/media/IAudioServerStateDispatcher.aidl",
- "media/java/android/media/IMediaController2.aidl",
"media/java/android/media/IMediaHTTPConnection.aidl",
"media/java/android/media/IMediaHTTPService.aidl",
"media/java/android/media/IMediaResourceMonitor.aidl",
"media/java/android/media/IMediaRouterClient.aidl",
"media/java/android/media/IMediaRouterService.aidl",
- "media/java/android/media/IMediaSession2.aidl",
- "media/java/android/media/IMediaSession2Service.aidl",
"media/java/android/media/IMediaScannerListener.aidl",
"media/java/android/media/IMediaScannerService.aidl",
"media/java/android/media/IPlaybackConfigDispatcher.aidl",
@@ -501,11 +504,7 @@
"media/java/android/media/session/ICallback.aidl",
"media/java/android/media/session/IOnMediaKeyListener.aidl",
"media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl",
- "media/java/android/media/session/ISession.aidl",
"media/java/android/media/session/ISession2TokensListener.aidl",
- "media/java/android/media/session/ISessionCallback.aidl",
- "media/java/android/media/session/ISessionController.aidl",
- "media/java/android/media/session/ISessionControllerCallback.aidl",
"media/java/android/media/session/ISessionManager.aidl",
"media/java/android/media/soundtrigger/ISoundTriggerDetectionService.aidl",
"media/java/android/media/soundtrigger/ISoundTriggerDetectionServiceClient.aidl",
@@ -520,8 +519,6 @@
"media/java/android/media/tv/ITvInputSessionCallback.aidl",
"media/java/android/media/tv/ITvRemoteProvider.aidl",
"media/java/android/media/tv/ITvRemoteServiceInput.aidl",
- "media/java/android/service/media/IMediaBrowserService.aidl",
- "media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl",
"telecomm/java/com/android/internal/telecom/ICallRedirectionAdapter.aidl",
"telecomm/java/com/android/internal/telecom/ICallRedirectionService.aidl",
"telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl",
@@ -634,6 +631,7 @@
"wifi/java/android/net/wifi/ISoftApCallback.aidl",
"wifi/java/android/net/wifi/ITrafficStateCallback.aidl",
"wifi/java/android/net/wifi/IWifiManager.aidl",
+ "wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl",
"wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl",
"wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl",
"wifi/java/android/net/wifi/aware/IWifiAwareMacAddressProvider.aidl",
@@ -688,6 +686,7 @@
"location/java",
"lowpan/java",
"media/java",
+ "media/apex/java",
"media/mca/effect/java",
"media/mca/filterfw/java",
"media/mca/filterpacks/java",
@@ -720,8 +719,6 @@
exclude_srcs: [
// See comment on framework-atb-backward-compatibility module below
"core/java/android/content/pm/AndroidTestBaseUpdater.java",
- // See comment on framework-oahl-backward-compatibility module below
- "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java",
],
no_framework_libs: true,
@@ -729,10 +726,13 @@
"ext",
],
+ jarjar_rules: ":framework-hidl-jarjar",
+
static_libs: [
"apex_aidl_interface-java",
"networkstack-aidl-interfaces-java",
"framework-protos",
+ "game-driver-protos",
"mediaplayer2-protos",
"android.hidl.base-V1.0-java",
"android.hardware.cas-V1.0-java",
@@ -745,6 +745,7 @@
"android.hardware.tv.input-V1.0-java-constants",
"android.hardware.usb-V1.0-java-constants",
"android.hardware.usb-V1.1-java-constants",
+ "android.hardware.usb-V1.2-java-constants",
"android.hardware.vibrator-V1.0-java",
"android.hardware.vibrator-V1.1-java",
"android.hardware.vibrator-V1.2-java",
@@ -762,8 +763,6 @@
required: [
// TODO: remove gps_debug when the build system propagates "required" properly.
"gps_debug.conf",
- // Loaded with System.loadLibrary by android.view.textclassifier
- "libmedia2_jni",
],
dxflags: [
@@ -789,6 +788,11 @@
],
}
+filegroup {
+ name: "framework-hidl-jarjar",
+ srcs: ["jarjar_rules_hidl.txt"],
+}
+
java_library {
name: "framework",
defaults: ["framework-defaults"],
@@ -799,11 +803,7 @@
name: "framework-annotation-proc",
defaults: ["framework-defaults"],
// Use UsedByApps annotation processor
- annotation_processors: ["unsupportedappusage-annotation-processor"],
- // b/25860419: annotation processors must be explicitly specified for grok
- annotation_processor_classes: [
- "android.processor.unsupportedappusage.UsedByAppsProcessor",
- ],
+ plugins: ["unsupportedappusage-annotation-processor"],
}
// A host library including just UnsupportedAppUsage.java so that the annotation
@@ -884,6 +884,7 @@
srcs: [
"core/java/android/net/ApfCapabilitiesParcelable.aidl",
"core/java/android/net/DhcpResultsParcelable.aidl",
+ "core/java/android/net/ICaptivePortal.aidl",
"core/java/android/net/INetworkMonitor.aidl",
"core/java/android/net/INetworkMonitorCallbacks.aidl",
"core/java/android/net/IIpMemoryStore.aidl",
@@ -1261,7 +1262,7 @@
":non_openjdk_javadoc_files",
":android_icu4j_src_files_for_docs",
":conscrypt_public_api_files",
- ":media2-srcs",
+ ":media-srcs-without-aidls",
"test-mock/src/**/*.java",
"test-runner/src/**/*.java",
],
@@ -1323,7 +1324,7 @@
":non_openjdk_javadoc_files",
":android_icu4j_src_files_for_docs",
":conscrypt_public_api_files",
- ":media2-srcs",
+ ":media-srcs-without-aidls",
],
srcs_lib: "framework",
srcs_lib_whitelist_dirs: frameworks_base_subdirs,
@@ -1769,6 +1770,7 @@
name: "framework-media-annotation-srcs",
srcs: [
"core/java/android/annotation/CallbackExecutor.java",
+ "core/java/android/annotation/CallSuper.java",
"core/java/android/annotation/DrawableRes.java",
"core/java/android/annotation/IntDef.java",
"core/java/android/annotation/LongDef.java",
@@ -1777,6 +1779,7 @@
"core/java/android/annotation/RequiresPermission.java",
"core/java/android/annotation/SdkConstant.java",
"core/java/android/annotation/StringDef.java",
+ "core/java/android/annotation/SystemApi.java",
"core/java/android/annotation/TestApi.java",
"core/java/android/annotation/UnsupportedAppUsage.java",
"core/java/com/android/internal/annotations/GuardedBy.java",
diff --git a/api/current.txt b/api/current.txt
index 96508c7..abb0e21 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3941,7 +3941,8 @@
method public boolean isBackgroundRestricted();
method @Deprecated public boolean isInLockTaskMode();
method public boolean isLowRamDevice();
- method public static boolean isRunningInTestHarness();
+ method @Deprecated public static boolean isRunningInTestHarness();
+ method public static boolean isRunningInUserTestHarness();
method public static boolean isUserAMonkey();
method @RequiresPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) public void killBackgroundProcesses(String);
method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int);
@@ -5792,6 +5793,7 @@
method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
method @Nullable public String getNotificationDelegate();
method public android.app.NotificationManager.Policy getNotificationPolicy();
+ method public boolean isNotificationAssistantAccessGranted(android.content.ComponentName);
method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
method public boolean isNotificationPolicyAccessGranted();
method public void notify(int, android.app.Notification);
@@ -6577,7 +6579,6 @@
}
public class DevicePolicyManager {
- method public void addCrossProfileCalendarPackage(@NonNull android.content.ComponentName, @NonNull String);
method public void addCrossProfileIntentFilter(@NonNull android.content.ComponentName, android.content.IntentFilter, int);
method public boolean addCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String);
method public int addOverrideApn(@NonNull android.content.ComponentName, @NonNull android.telephony.data.ApnSetting);
@@ -6607,7 +6608,7 @@
method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName);
method public boolean getCameraDisabled(@Nullable android.content.ComponentName);
method @Deprecated @Nullable public String getCertInstallerPackage(@NonNull android.content.ComponentName) throws java.lang.SecurityException;
- method @NonNull public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
+ method @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
method public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName);
method public boolean getCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName);
method @NonNull public java.util.List<java.lang.String> getCrossProfileWidgetProviders(@NonNull android.content.ComponentName);
@@ -6697,7 +6698,6 @@
method public int logoutUser(@NonNull android.content.ComponentName);
method public void reboot(@NonNull android.content.ComponentName);
method public void removeActiveAdmin(@NonNull android.content.ComponentName);
- method public boolean removeCrossProfileCalendarPackage(@NonNull android.content.ComponentName, @NonNull String);
method public boolean removeCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String);
method public boolean removeKeyPair(@Nullable android.content.ComponentName, @NonNull String);
method public boolean removeOverrideApn(@NonNull android.content.ComponentName, int);
@@ -6719,6 +6719,7 @@
method public void setBluetoothContactSharingDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCameraDisabled(@NonNull android.content.ComponentName, boolean);
method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
+ method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean);
method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>);
@@ -6822,6 +6823,7 @@
field public static final String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
field public static final String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
field public static final String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
+ field @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY) public static final String EXTRA_PASSWORD_COMPLEXITY = "android.app.extra.PASSWORD_COMPLEXITY";
field public static final String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
field public static final String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE";
field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME";
@@ -8608,6 +8610,13 @@
method @Deprecated @BinderThread public void onHealthChannelStateChange(android.bluetooth.BluetoothHealthAppConfiguration, android.bluetooth.BluetoothDevice, int, int, android.os.ParcelFileDescriptor, int);
}
+ public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
+ method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method public int getConnectionState(android.bluetooth.BluetoothDevice);
+ method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+ field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
+ }
+
public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
method public boolean connect(android.bluetooth.BluetoothDevice);
method public boolean disconnect(android.bluetooth.BluetoothDevice);
@@ -8703,6 +8712,7 @@
field public static final int GATT_SERVER = 8; // 0x8
field public static final int HEADSET = 1; // 0x1
field @Deprecated public static final int HEALTH = 3; // 0x3
+ field public static final int HEARING_AID = 21; // 0x15
field public static final int HID_DEVICE = 19; // 0x13
field public static final int SAP = 10; // 0xa
field public static final int STATE_CONNECTED = 2; // 0x2
@@ -9625,7 +9635,7 @@
public abstract class Context {
ctor public Context();
- method public abstract boolean bindIsolatedService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String);
+ method public boolean bindIsolatedService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String);
method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int);
method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
@@ -9678,7 +9688,7 @@
method public abstract java.io.File getNoBackupFilesDir();
method public abstract java.io.File getObbDir();
method public abstract java.io.File[] getObbDirs();
- method public abstract String getOpPackageName();
+ method public String getOpPackageName();
method public abstract String getPackageCodePath();
method public abstract android.content.pm.PackageManager getPackageManager();
method public abstract String getPackageName();
@@ -9745,7 +9755,7 @@
method public abstract void unbindService(@NonNull android.content.ServiceConnection);
method public void unregisterComponentCallbacks(android.content.ComponentCallbacks);
method public abstract void unregisterReceiver(android.content.BroadcastReceiver);
- method public abstract void updateServiceGroup(@NonNull android.content.ServiceConnection, int, int);
+ method public void updateServiceGroup(@NonNull android.content.ServiceConnection, int, int);
field public static final String ACCESSIBILITY_SERVICE = "accessibility";
field public static final String ACCOUNT_SERVICE = "account";
field public static final String ACTIVITY_SERVICE = "activity";
@@ -9839,7 +9849,6 @@
public class ContextWrapper extends android.content.Context {
ctor public ContextWrapper(android.content.Context);
method protected void attachBaseContext(android.content.Context);
- method public boolean bindIsolatedService(android.content.Intent, android.content.ServiceConnection, int, String);
method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
method public int checkCallingOrSelfPermission(String);
method public int checkCallingOrSelfUriPermission(android.net.Uri, int);
@@ -9889,7 +9898,6 @@
method public java.io.File getNoBackupFilesDir();
method public java.io.File getObbDir();
method public java.io.File[] getObbDirs();
- method public String getOpPackageName();
method public String getPackageCodePath();
method public android.content.pm.PackageManager getPackageManager();
method public String getPackageName();
@@ -9945,7 +9953,6 @@
method public boolean stopService(android.content.Intent);
method public void unbindService(android.content.ServiceConnection);
method public void unregisterReceiver(android.content.BroadcastReceiver);
- method public void updateServiceGroup(android.content.ServiceConnection, int, int);
}
@Deprecated public class CursorLoader extends android.content.AsyncTaskLoader<android.database.Cursor> {
@@ -10221,7 +10228,7 @@
field public static final String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED";
field public static final String ACTION_MY_PACKAGE_SUSPENDED = "android.intent.action.MY_PACKAGE_SUSPENDED";
field public static final String ACTION_MY_PACKAGE_UNSUSPENDED = "android.intent.action.MY_PACKAGE_UNSUSPENDED";
- field public static final String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
+ field @Deprecated public static final String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
field public static final String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
field public static final String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
field public static final String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
@@ -10349,6 +10356,7 @@
field public static final int EXTRA_DOCK_STATE_LE_DESK = 3; // 0x3
field public static final int EXTRA_DOCK_STATE_UNDOCKED = 0; // 0x0
field public static final String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP";
+ field public static final String EXTRA_DURATION_MILLIS = "android.intent.extra.DURATION_MILLIS";
field public static final String EXTRA_EMAIL = "android.intent.extra.EMAIL";
field public static final String EXTRA_EXCLUDE_COMPONENTS = "android.intent.extra.EXCLUDE_COMPONENTS";
field public static final String EXTRA_FROM_STORAGE = "android.intent.extra.FROM_STORAGE";
@@ -11210,6 +11218,7 @@
public class LauncherApps {
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(String, android.os.UserHandle);
+ method @Nullable public android.content.pm.LauncherApps.AppUsageLimit getAppUsageLimit(String, android.os.UserHandle);
method public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent);
method public java.util.List<android.os.UserHandle> getProfiles();
@@ -11237,6 +11246,14 @@
field public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
}
+ public static final class LauncherApps.AppUsageLimit implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getTotalUsageLimit();
+ method public long getUsageRemaining();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.LauncherApps.AppUsageLimit> CREATOR;
+ }
+
public abstract static class LauncherApps.Callback {
ctor public LauncherApps.Callback();
method public abstract void onPackageAdded(String, android.os.UserHandle);
@@ -11433,6 +11450,7 @@
method public void setAppIcon(@Nullable android.graphics.Bitmap);
method public void setAppLabel(@Nullable CharSequence);
method public void setAppPackageName(@Nullable String);
+ method public void setInstallAsApex();
method public void setInstallLocation(int);
method public void setInstallReason(int);
method public void setMultiPackage();
@@ -11500,7 +11518,7 @@
method public abstract java.util.List<android.content.pm.PermissionGroupInfo> getAllPermissionGroups(int);
method public abstract android.graphics.drawable.Drawable getApplicationBanner(android.content.pm.ApplicationInfo);
method public abstract android.graphics.drawable.Drawable getApplicationBanner(String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public abstract int getApplicationEnabledSetting(String);
+ method public abstract int getApplicationEnabledSetting(@NonNull String);
method public abstract android.graphics.drawable.Drawable getApplicationIcon(android.content.pm.ApplicationInfo);
method public abstract android.graphics.drawable.Drawable getApplicationIcon(String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.ApplicationInfo getApplicationInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -11508,7 +11526,7 @@
method public abstract android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
method public abstract android.graphics.drawable.Drawable getApplicationLogo(String) throws android.content.pm.PackageManager.NameNotFoundException;
method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
- method public abstract int getComponentEnabledSetting(android.content.ComponentName);
+ method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
method public abstract android.graphics.drawable.Drawable getDrawable(String, @DrawableRes int, android.content.pm.ApplicationInfo);
method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
@@ -11574,8 +11592,8 @@
method public abstract android.content.pm.ProviderInfo resolveContentProvider(String, int);
method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
method public abstract void setApplicationCategoryHint(@NonNull String, int);
- method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(String, int, int);
- method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
+ method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(@NonNull String, int, int);
+ method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(@NonNull android.content.ComponentName, int, int);
method public abstract void setInstallerPackageName(String, String);
method public abstract void updateInstantAppCookie(@Nullable byte[]);
method public abstract void verifyPendingInstall(int, int);
@@ -11655,6 +11673,7 @@
field public static final String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape";
field public static final String FEATURE_SCREEN_PORTRAIT = "android.hardware.screen.portrait";
field public static final String FEATURE_SECURELY_REMOVES_USERS = "android.software.securely_removes_users";
+ field public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen";
field public static final String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer";
field public static final String FEATURE_SENSOR_AMBIENT_TEMPERATURE = "android.hardware.sensor.ambient_temperature";
field public static final String FEATURE_SENSOR_BAROMETER = "android.hardware.sensor.barometer";
@@ -11674,6 +11693,7 @@
field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
+ field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
field public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television";
field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
@@ -14618,8 +14638,8 @@
public class Picture {
ctor public Picture();
ctor public Picture(android.graphics.Picture);
- method public android.graphics.Canvas beginRecording(int, int);
- method public void draw(android.graphics.Canvas);
+ method @NonNull public android.graphics.Canvas beginRecording(int, int);
+ method public void draw(@NonNull android.graphics.Canvas);
method public void endRecording();
method public int getHeight();
method public int getWidth();
@@ -14871,7 +14891,7 @@
}
public final class RenderNode {
- ctor public RenderNode(String);
+ ctor public RenderNode(@Nullable String);
method public int computeApproximateMemoryUsage();
method public void discardDisplayList();
method public void endRecording();
@@ -17077,6 +17097,7 @@
field public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_POSE_TRANSLATION;
field @Deprecated public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_RADIAL_DISTORTION;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> LENS_STATE;
+ field public static final android.hardware.camera2.CaptureResult.Key<java.lang.String> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> NOISE_REDUCTION_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
@@ -21650,7 +21671,7 @@
ctor public JapaneseCalendar(int, int, int, int);
ctor public JapaneseCalendar(int, int, int);
ctor public JapaneseCalendar(int, int, int, int, int, int);
- field public static final int CURRENT_ERA;
+ field @Deprecated public static final int CURRENT_ERA;
field public static final int HEISEI;
field public static final int MEIJI;
field public static final int SHOWA;
@@ -22256,7 +22277,7 @@
method public void onUpdateExtractingViews(android.view.inputmethod.EditorInfo);
method public void onUpdateExtractingVisibility(android.view.inputmethod.EditorInfo);
method public void onUpdateSelection(int, int, int, int, int, int);
- method public void onViewClicked(boolean);
+ method @Deprecated public void onViewClicked(boolean);
method public void onWindowHidden();
method public void onWindowShown();
method public void requestHideSelf(int);
@@ -24638,6 +24659,7 @@
method @android.media.MediaDrm.SecurityLevel public int getSecurityLevel(@NonNull byte[]);
method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID);
method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String);
+ method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, @android.media.MediaDrm.SecurityLevel int);
method @NonNull public byte[] openSession() throws android.media.NotProvisionedException, android.media.ResourceBusyException;
method @NonNull public byte[] openSession(@android.media.MediaDrm.SecurityLevel int) throws android.media.NotProvisionedException, android.media.ResourceBusyException;
method @Nullable public byte[] provideKeyResponse(@NonNull byte[], @NonNull byte[]) throws android.media.DeniedByServerException, android.media.NotProvisionedException;
@@ -25407,6 +25429,7 @@
method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public Object attachAuxEffect(int);
method public boolean cancelCommand(@NonNull Object);
+ method public void clearDrmEventCallback();
method public Object clearNextDataSources();
method public void clearPendingCommands();
method public void close();
@@ -25915,6 +25938,23 @@
method @Nullable public android.media.Session2Command.Result onSessionCommand(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo, @NonNull android.media.Session2Command, @Nullable android.os.Bundle);
}
+ public abstract class MediaSession2Service extends android.app.Service {
+ ctor public MediaSession2Service();
+ method public final void addSession(@NonNull android.media.MediaSession2);
+ method @NonNull public final java.util.List<android.media.MediaSession2> getSessions();
+ method @CallSuper @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+ method @NonNull public abstract android.media.MediaSession2 onGetPrimarySession();
+ method @Nullable public abstract android.media.MediaSession2Service.MediaNotification onUpdateNotification(@NonNull android.media.MediaSession2);
+ method public final void removeSession(@NonNull android.media.MediaSession2);
+ field public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service";
+ }
+
+ public static class MediaSession2Service.MediaNotification {
+ ctor public MediaSession2Service.MediaNotification(int, @NonNull android.app.Notification);
+ method @NonNull public android.app.Notification getNotification();
+ method public int getNotificationId();
+ }
+
public final class MediaSync {
ctor public MediaSync();
method @NonNull public android.view.Surface createInputSurface();
@@ -26577,12 +26617,9 @@
field public static final int SUCCESS = 0; // 0x0
}
- public static final class AudioEffect.Descriptor implements android.os.Parcelable {
+ public static class AudioEffect.Descriptor {
ctor public AudioEffect.Descriptor();
ctor public AudioEffect.Descriptor(String, String, String, String, String);
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.media.audiofx.AudioEffect.Descriptor> CREATOR;
field public String connectMode;
field public String implementor;
field public String name;
@@ -27426,18 +27463,26 @@
public final class MediaSessionManager {
method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName);
method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName, @Nullable android.os.Handler);
+ method public void addOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener);
+ method public void addOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener, @NonNull android.os.Handler);
method @NonNull public java.util.List<android.media.session.MediaController> getActiveSessions(@Nullable android.content.ComponentName);
+ method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens();
method public boolean isTrustedForMediaControl(@NonNull android.media.session.MediaSessionManager.RemoteUserInfo);
+ method public void notifySession2Created(@NonNull android.media.Session2Token);
method public void removeOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener);
+ method public void removeOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener);
}
public static interface MediaSessionManager.OnActiveSessionsChangedListener {
method public void onActiveSessionsChanged(@Nullable java.util.List<android.media.session.MediaController>);
}
+ public static interface MediaSessionManager.OnSession2TokensChangedListener {
+ method public void onSession2TokensChanged(@NonNull java.util.List<android.media.Session2Token>);
+ }
+
public static final class MediaSessionManager.RemoteUserInfo {
ctor public MediaSessionManager.RemoteUserInfo(@NonNull String, int, int);
- ctor public MediaSessionManager.RemoteUserInfo(String, int, int, android.os.IBinder);
method public String getPackageName();
method public int getPid();
method public int getUid();
@@ -28446,6 +28491,7 @@
public class ConnectivityManager {
method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
method public boolean bindProcessToNetwork(@Nullable android.net.Network);
+ method public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @Nullable public android.net.Network getActiveNetwork();
method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo();
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo();
@@ -28555,6 +28601,28 @@
field public int serverAddress;
}
+ public final class DnsResolver {
+ method public static android.net.DnsResolver getInstance();
+ method public void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException;
+ method public void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException;
+ method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.InetAddressAnswerListener) throws android.system.ErrnoException;
+ field public static final int CLASS_IN = 1; // 0x1
+ field public static final int FLAG_EMPTY = 0; // 0x0
+ field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4
+ field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2
+ field public static final int FLAG_NO_RETRY = 1; // 0x1
+ field public static final int TYPE_A = 1; // 0x1
+ field public static final int TYPE_AAAA = 28; // 0x1c
+ }
+
+ public static interface DnsResolver.InetAddressAnswerListener {
+ method public void onAnswer(@NonNull java.util.List<java.net.InetAddress>);
+ }
+
+ public static interface DnsResolver.RawAnswerListener {
+ method public void onAnswer(@Nullable byte[]);
+ }
+
public class InetAddresses {
method public static boolean isNumericAddress(String);
method public static java.net.InetAddress parseNumericAddress(String);
@@ -28903,24 +28971,24 @@
field public static final android.os.Parcelable.Creator<android.net.RouteInfo> CREATOR;
}
- public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
+ @Deprecated public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
ctor @Deprecated public SSLCertificateSocketFactory(int);
- method public java.net.Socket createSocket(java.net.Socket, String, int, boolean) throws java.io.IOException;
- method public java.net.Socket createSocket(java.net.InetAddress, int, java.net.InetAddress, int) throws java.io.IOException;
- method public java.net.Socket createSocket(java.net.InetAddress, int) throws java.io.IOException;
- method public java.net.Socket createSocket(String, int, java.net.InetAddress, int) throws java.io.IOException;
- method public java.net.Socket createSocket(String, int) throws java.io.IOException;
- method public static javax.net.SocketFactory getDefault(int);
- method public static javax.net.ssl.SSLSocketFactory getDefault(int, android.net.SSLSessionCache);
- method public String[] getDefaultCipherSuites();
- method public static javax.net.ssl.SSLSocketFactory getInsecure(int, android.net.SSLSessionCache);
- method public byte[] getNpnSelectedProtocol(java.net.Socket);
- method public String[] getSupportedCipherSuites();
- method public void setHostname(java.net.Socket, String);
- method public void setKeyManagers(javax.net.ssl.KeyManager[]);
- method public void setNpnProtocols(byte[][]);
- method public void setTrustManagers(javax.net.ssl.TrustManager[]);
- method public void setUseSessionTickets(java.net.Socket, boolean);
+ method @Deprecated public java.net.Socket createSocket(java.net.Socket, String, int, boolean) throws java.io.IOException;
+ method @Deprecated public java.net.Socket createSocket(java.net.InetAddress, int, java.net.InetAddress, int) throws java.io.IOException;
+ method @Deprecated public java.net.Socket createSocket(java.net.InetAddress, int) throws java.io.IOException;
+ method @Deprecated public java.net.Socket createSocket(String, int, java.net.InetAddress, int) throws java.io.IOException;
+ method @Deprecated public java.net.Socket createSocket(String, int) throws java.io.IOException;
+ method @Deprecated public static javax.net.SocketFactory getDefault(int);
+ method @Deprecated public static javax.net.ssl.SSLSocketFactory getDefault(int, android.net.SSLSessionCache);
+ method @Deprecated public String[] getDefaultCipherSuites();
+ method @Deprecated public static javax.net.ssl.SSLSocketFactory getInsecure(int, android.net.SSLSessionCache);
+ method @Deprecated public byte[] getNpnSelectedProtocol(java.net.Socket);
+ method @Deprecated public String[] getSupportedCipherSuites();
+ method @Deprecated public void setHostname(java.net.Socket, String);
+ method @Deprecated public void setKeyManagers(javax.net.ssl.KeyManager[]);
+ method @Deprecated public void setNpnProtocols(byte[][]);
+ method @Deprecated public void setTrustManagers(javax.net.ssl.TrustManager[]);
+ method @Deprecated public void setUseSessionTickets(java.net.Socket, boolean);
}
public final class SSLSessionCache {
@@ -28928,6 +28996,29 @@
ctor public SSLSessionCache(android.content.Context);
}
+ public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+ method public final void close();
+ method public final void start(@IntRange(from=0xa, to=0xe10) int);
+ method public final void stop();
+ field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1
+ field public static final int ERROR_HARDWARE_UNSUPPORTED = -30; // 0xffffffe2
+ field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8
+ field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
+ field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9
+ field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec
+ field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea
+ field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7
+ field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6
+ }
+
+ public static class SocketKeepalive.Callback {
+ ctor public SocketKeepalive.Callback();
+ method public void onDataReceived();
+ method public void onError(int);
+ method public void onStarted();
+ method public void onStopped();
+ }
+
public class TrafficStats {
ctor public TrafficStats();
method public static void clearThreadStatsTag();
@@ -29109,6 +29200,8 @@
public class VpnService extends android.app.Service {
ctor public VpnService();
+ method public final boolean isAlwaysOn();
+ method public final boolean isLockdownEnabled();
method public android.os.IBinder onBind(android.content.Intent);
method public void onRevoke();
method public static android.content.Intent prepare(android.content.Context);
@@ -29136,6 +29229,7 @@
method public android.os.ParcelFileDescriptor establish();
method public android.net.VpnService.Builder setBlocking(boolean);
method public android.net.VpnService.Builder setConfigureIntent(android.app.PendingIntent);
+ method public android.net.VpnService.Builder setHttpProxy(android.net.ProxyInfo);
method public android.net.VpnService.Builder setMtu(int);
method public android.net.VpnService.Builder setSession(String);
method public android.net.VpnService.Builder setUnderlyingNetworks(android.net.Network[]);
@@ -34864,10 +34958,13 @@
method public static final void setThreadPriority(int, int) throws java.lang.IllegalArgumentException, java.lang.SecurityException;
method public static final void setThreadPriority(int) throws java.lang.IllegalArgumentException, java.lang.SecurityException;
method @Deprecated public static final boolean supportsProcesses();
+ field public static final int BLUETOOTH_UID = 1002; // 0x3ea
field public static final int FIRST_APPLICATION_UID = 10000; // 0x2710
field public static final int INVALID_UID = -1; // 0xffffffff
field public static final int LAST_APPLICATION_UID = 19999; // 0x4e1f
field public static final int PHONE_UID = 1001; // 0x3e9
+ field public static final int ROOT_UID = 0; // 0x0
+ field public static final int SHELL_UID = 2000; // 0x7d0
field public static final int SIGNAL_KILL = 9; // 0x9
field public static final int SIGNAL_QUIT = 3; // 0x3
field public static final int SIGNAL_USR1 = 10; // 0xa
@@ -35199,11 +35296,16 @@
public abstract class VibrationEffect implements android.os.Parcelable {
method public static android.os.VibrationEffect createOneShot(long, int);
+ method public static android.os.VibrationEffect createPrebaked(int);
method public static android.os.VibrationEffect createWaveform(long[], int);
method public static android.os.VibrationEffect createWaveform(long[], int[], int);
method public int describeContents();
field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+ field public static final int EFFECT_CLICK = 0; // 0x0
+ field public static final int EFFECT_DOUBLE_CLICK = 1; // 0x1
+ field public static final int EFFECT_HEAVY_CLICK = 5; // 0x5
+ field public static final int EFFECT_TICK = 2; // 0x2
}
public abstract class Vibrator {
@@ -37882,18 +37984,18 @@
method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException;
method @Deprecated public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
method public static String getDocumentId(android.net.Uri);
- method public static android.os.Bundle getDocumentMetadata(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException;
+ method @Nullable public static android.os.Bundle getDocumentMetadata(@NonNull android.content.ContentInterface, @NonNull android.net.Uri) throws java.io.FileNotFoundException;
method @Deprecated public static android.os.Bundle getDocumentMetadata(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentInterface, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method @Deprecated public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public static String getRootId(android.net.Uri);
method public static String getSearchDocumentsQuery(android.net.Uri);
method public static String getTreeDocumentId(android.net.Uri);
- method public static boolean isChildDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
+ method public static boolean isChildDocument(@NonNull android.content.ContentInterface, @NonNull android.net.Uri, @NonNull android.net.Uri) throws java.io.FileNotFoundException;
method @Deprecated public static boolean isChildDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
method public static boolean isDocumentUri(android.content.Context, @Nullable android.net.Uri);
- method public static boolean isRootUri(android.content.Context, @Nullable android.net.Uri);
- method public static boolean isRootsUri(android.content.Context, @Nullable android.net.Uri);
+ method public static boolean isRootUri(@NonNull android.content.Context, @Nullable android.net.Uri);
+ method public static boolean isRootsUri(@NonNull android.content.Context, @Nullable android.net.Uri);
method public static boolean isTreeUri(android.net.Uri);
method public static android.net.Uri moveDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
method @Deprecated public static android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
@@ -37964,6 +38066,7 @@
field public static final String COLUMN_FLAGS = "flags";
field public static final String COLUMN_ICON = "icon";
field public static final String COLUMN_MIME_TYPES = "mime_types";
+ field public static final String COLUMN_QUERY_ARGS = "query_args";
field public static final String COLUMN_ROOT_ID = "root_id";
field public static final String COLUMN_SUMMARY = "summary";
field public static final String COLUMN_TITLE = "title";
@@ -37986,7 +38089,7 @@
method public void deleteDocument(String) throws java.io.FileNotFoundException;
method public void ejectRoot(String);
method public android.provider.DocumentsContract.Path findDocumentPath(@Nullable String, String) throws java.io.FileNotFoundException;
- method @Nullable public android.os.Bundle getDocumentMetadata(String) throws java.io.FileNotFoundException;
+ method @Nullable public android.os.Bundle getDocumentMetadata(@NonNull String) throws java.io.FileNotFoundException;
method public String[] getDocumentStreamTypes(String, String);
method public String getDocumentType(String) throws java.io.FileNotFoundException;
method public final String getType(android.net.Uri);
@@ -38011,7 +38114,7 @@
method public android.database.Cursor queryRecentDocuments(String, String[], @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public abstract android.database.Cursor queryRoots(String[]) throws java.io.FileNotFoundException;
method public android.database.Cursor querySearchDocuments(String, String, String[]) throws java.io.FileNotFoundException;
- method public android.database.Cursor querySearchDocuments(String, String[], android.os.Bundle) throws java.io.FileNotFoundException;
+ method public android.database.Cursor querySearchDocuments(@NonNull String, @Nullable String[], @NonNull android.os.Bundle) throws java.io.FileNotFoundException;
method public void removeDocument(String, String) throws java.io.FileNotFoundException;
method public String renameDocument(String, String) throws java.io.FileNotFoundException;
method public final void revokeDocumentPermission(String);
@@ -38143,6 +38246,8 @@
field public static final String MEDIA_SCANNER_VOLUME = "volume";
field public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE = "android.media.still_image_camera_preview_service";
field public static final String UNKNOWN_STRING = "<unknown>";
+ field public static final String VOLUME_EXTERNAL = "external";
+ field public static final String VOLUME_INTERNAL = "internal";
}
public static final class MediaStore.Audio {
@@ -38349,28 +38454,28 @@
field public static final android.net.Uri INTERNAL_CONTENT_URI;
}
- public static class MediaStore.Images.Thumbnails implements android.provider.BaseColumns {
- ctor public MediaStore.Images.Thumbnails();
+ @Deprecated public static class MediaStore.Images.Thumbnails implements android.provider.BaseColumns {
+ ctor @Deprecated public MediaStore.Images.Thumbnails();
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
- method public static android.net.Uri getContentUri(String);
+ method @Deprecated public static android.net.Uri getContentUri(String);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
- method public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
- method public static final android.database.Cursor queryMiniThumbnail(android.content.ContentResolver, long, int, String[]);
- method public static final android.database.Cursor queryMiniThumbnails(android.content.ContentResolver, android.net.Uri, int, String[]);
+ method @Deprecated public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
+ method @Deprecated public static final android.database.Cursor queryMiniThumbnail(android.content.ContentResolver, long, int, String[]);
+ method @Deprecated public static final android.database.Cursor queryMiniThumbnails(android.content.ContentResolver, android.net.Uri, int, String[]);
field @Deprecated public static final String DATA = "_data";
- field public static final String DEFAULT_SORT_ORDER = "image_id ASC";
- field public static final android.net.Uri EXTERNAL_CONTENT_URI;
- field public static final int FULL_SCREEN_KIND = 2; // 0x2
- field public static final String HEIGHT = "height";
- field public static final String IMAGE_ID = "image_id";
- field public static final android.net.Uri INTERNAL_CONTENT_URI;
- field public static final String KIND = "kind";
- field public static final int MICRO_KIND = 3; // 0x3
- field public static final int MINI_KIND = 1; // 0x1
- field public static final String THUMB_DATA = "thumb_data";
- field public static final String WIDTH = "width";
+ field @Deprecated public static final String DEFAULT_SORT_ORDER = "image_id ASC";
+ field @Deprecated public static final android.net.Uri EXTERNAL_CONTENT_URI;
+ field @Deprecated public static final int FULL_SCREEN_KIND = 2; // 0x2
+ field @Deprecated public static final String HEIGHT = "height";
+ field @Deprecated public static final String IMAGE_ID = "image_id";
+ field @Deprecated public static final android.net.Uri INTERNAL_CONTENT_URI;
+ field @Deprecated public static final String KIND = "kind";
+ field @Deprecated public static final int MICRO_KIND = 3; // 0x3
+ field @Deprecated public static final int MINI_KIND = 1; // 0x1
+ field @Deprecated public static final String THUMB_DATA = "thumb_data";
+ field @Deprecated public static final String WIDTH = "width";
}
public static interface MediaStore.MediaColumns extends android.provider.BaseColumns {
@@ -38422,24 +38527,24 @@
field public static final android.net.Uri INTERNAL_CONTENT_URI;
}
- public static class MediaStore.Video.Thumbnails implements android.provider.BaseColumns {
- ctor public MediaStore.Video.Thumbnails();
+ @Deprecated public static class MediaStore.Video.Thumbnails implements android.provider.BaseColumns {
+ ctor @Deprecated public MediaStore.Video.Thumbnails();
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
- method public static android.net.Uri getContentUri(String);
+ method @Deprecated public static android.net.Uri getContentUri(String);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
field @Deprecated public static final String DATA = "_data";
- field public static final String DEFAULT_SORT_ORDER = "video_id ASC";
- field public static final android.net.Uri EXTERNAL_CONTENT_URI;
- field public static final int FULL_SCREEN_KIND = 2; // 0x2
- field public static final String HEIGHT = "height";
- field public static final android.net.Uri INTERNAL_CONTENT_URI;
- field public static final String KIND = "kind";
- field public static final int MICRO_KIND = 3; // 0x3
- field public static final int MINI_KIND = 1; // 0x1
- field public static final String VIDEO_ID = "video_id";
- field public static final String WIDTH = "width";
+ field @Deprecated public static final String DEFAULT_SORT_ORDER = "video_id ASC";
+ field @Deprecated public static final android.net.Uri EXTERNAL_CONTENT_URI;
+ field @Deprecated public static final int FULL_SCREEN_KIND = 2; // 0x2
+ field @Deprecated public static final String HEIGHT = "height";
+ field @Deprecated public static final android.net.Uri INTERNAL_CONTENT_URI;
+ field @Deprecated public static final String KIND = "kind";
+ field @Deprecated public static final int MICRO_KIND = 3; // 0x3
+ field @Deprecated public static final int MINI_KIND = 1; // 0x1
+ field @Deprecated public static final String VIDEO_ID = "video_id";
+ field @Deprecated public static final String WIDTH = "width";
}
public static interface MediaStore.Video.VideoColumns extends android.provider.MediaStore.MediaColumns {
@@ -39020,6 +39125,7 @@
field public static final String CONTENT_ID = "cid";
field public static final String CONTENT_LOCATION = "cl";
field public static final String CONTENT_TYPE = "ct";
+ field public static final android.net.Uri CONTENT_URI;
field public static final String CT_START = "ctt_s";
field public static final String CT_TYPE = "ctt_t";
field public static final String FILENAME = "fn";
@@ -41281,6 +41387,22 @@
package android.service.notification {
+ public final class Adjustment implements android.os.Parcelable {
+ ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int);
+ method public int describeContents();
+ method public CharSequence getExplanation();
+ method public String getKey();
+ method public String getPackage();
+ method public android.os.Bundle getSignals();
+ method public int getUser();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
+ field public static final String KEY_IMPORTANCE = "key_importance";
+ field public static final String KEY_SMART_ACTIONS = "key_smart_actions";
+ field public static final String KEY_SMART_REPLIES = "key_smart_replies";
+ field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
+ }
+
public final class Condition implements android.os.Parcelable {
ctor public Condition(android.net.Uri, String, int);
ctor public Condition(android.net.Uri, String, String, String, int, int, int);
@@ -41327,6 +41449,24 @@
field @Deprecated public static final String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
}
+ public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
+ ctor public NotificationAssistantService();
+ method public final void adjustNotification(android.service.notification.Adjustment);
+ method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
+ method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public void onNotificationDirectReplied(@NonNull String);
+ method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
+ method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
+ method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
+ method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
+ method public void onNotificationsSeen(java.util.List<java.lang.String>);
+ method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
+ field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
+ field public static final int SOURCE_FROM_APP = 0; // 0x0
+ field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1
+ }
+
public abstract class NotificationListenerService extends android.app.Service {
ctor public NotificationListenerService();
method public final void cancelAllNotifications();
@@ -41407,6 +41547,8 @@
method public long getLastAudiblyAlertedMillis();
method public String getOverrideGroupKey();
method public int getRank();
+ method public java.util.List<android.app.Notification.Action> getSmartActions();
+ method public java.util.List<java.lang.CharSequence> getSmartReplies();
method public int getSuppressedVisualEffects();
method public int getUserSentiment();
method public boolean isAmbient();
@@ -41425,6 +41567,37 @@
field public static final android.os.Parcelable.Creator<android.service.notification.NotificationListenerService.RankingMap> CREATOR;
}
+ public final class NotificationStats implements android.os.Parcelable {
+ ctor public NotificationStats();
+ method public int describeContents();
+ method public int getDismissalSentiment();
+ method public int getDismissalSurface();
+ method public boolean hasDirectReplied();
+ method public boolean hasExpanded();
+ method public boolean hasInteracted();
+ method public boolean hasSeen();
+ method public boolean hasSnoozed();
+ method public boolean hasViewedSettings();
+ method public void setDirectReplied();
+ method public void setDismissalSentiment(int);
+ method public void setDismissalSurface(int);
+ method public void setExpanded();
+ method public void setSeen();
+ method public void setSnoozed();
+ method public void setViewedSettings();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
+ field public static final int DISMISSAL_AOD = 2; // 0x2
+ field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
+ field public static final int DISMISSAL_OTHER = 0; // 0x0
+ field public static final int DISMISSAL_PEEK = 1; // 0x1
+ field public static final int DISMISSAL_SHADE = 3; // 0x3
+ field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
+ field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
+ field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
+ field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
+ }
+
public class StatusBarNotification implements android.os.Parcelable {
ctor @Deprecated public StatusBarNotification(String, String, int, String, int, int, int, android.app.Notification, android.os.UserHandle, long);
ctor public StatusBarNotification(android.os.Parcel);
@@ -41619,6 +41792,7 @@
public class VoiceInteractionService extends android.app.Service {
ctor public VoiceInteractionService();
+ method public final void clearTranscription(boolean);
method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
method public int getDisabledShowContext();
method public static boolean isActiveService(android.content.Context, android.content.ComponentName);
@@ -41628,9 +41802,15 @@
method public void onReady();
method public void onShutdown();
method public void setDisabledShowContext(int);
+ method public final void setTranscription(@NonNull String);
+ method public final void setVoiceState(int);
method public void showSession(android.os.Bundle, int);
field public static final String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
field public static final String SERVICE_META_DATA = "android.voice_interaction";
+ field public static final int VOICE_STATE_CONDITIONAL_LISTENING = 1; // 0x1
+ field public static final int VOICE_STATE_FULFILLING = 3; // 0x3
+ field public static final int VOICE_STATE_LISTENING = 2; // 0x2
+ field public static final int VOICE_STATE_NONE = 0; // 0x0
}
public class VoiceInteractionSession implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback {
@@ -42991,6 +43171,15 @@
method public abstract void onScreenCall(@NonNull android.telecom.Call.Details);
method public final void provideCallIdentification(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallIdentification);
method public final void respondToCall(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallScreeningService.CallResponse);
+ field public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED = "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED";
+ field public static final int CALL_DURATION_LONG = 4; // 0x4
+ field public static final int CALL_DURATION_MEDIUM = 3; // 0x3
+ field public static final int CALL_DURATION_SHORT = 2; // 0x2
+ field public static final int CALL_DURATION_VERY_SHORT = 1; // 0x1
+ field public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
+ field public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE";
+ field public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE";
+ field public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE";
field public static final String SERVICE_INTERFACE = "android.telecom.CallScreeningService";
}
@@ -43584,6 +43773,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
method public android.telecom.PhoneAccountHandle getSimCallManager();
method public String getSystemDialerPackage();
+ method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getUserSelectedOutgoingPhoneAccount();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber(android.telecom.PhoneAccountHandle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String, android.telecom.PhoneAccountHandle);
@@ -43595,6 +43785,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, String);
method @RequiresPermission(anyOf={android.Manifest.permission.CALL_PHONE, android.Manifest.permission.MANAGE_OWN_CALLS}) public void placeCall(android.net.Uri, android.os.Bundle);
method public void registerPhoneAccount(android.telecom.PhoneAccount);
+ method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void reportNuisanceCallStatus(@NonNull android.net.Uri, boolean);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger();
method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle);
@@ -44143,6 +44334,7 @@
public final class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
method public int getAsuLevel();
+ method public int getBitErrorRate();
method public int getDbm();
method public int getLevel();
method public int getTimingAdvance();
@@ -44444,16 +44636,16 @@
public class SignalStrength implements android.os.Parcelable {
method public int describeContents();
- method public int getCdmaDbm();
- method public int getCdmaEcio();
+ method @Deprecated public int getCdmaDbm();
+ method @Deprecated public int getCdmaEcio();
method @NonNull public java.util.List<android.telephony.CellSignalStrength> getCellSignalStrengths();
- method public int getEvdoDbm();
- method public int getEvdoEcio();
- method public int getEvdoSnr();
- method public int getGsmBitErrorRate();
- method public int getGsmSignalStrength();
+ method @Deprecated public int getEvdoDbm();
+ method @Deprecated public int getEvdoEcio();
+ method @Deprecated public int getEvdoSnr();
+ method @Deprecated public int getGsmBitErrorRate();
+ method @Deprecated public int getGsmSignalStrength();
method public int getLevel();
- method public boolean isGsm();
+ method @Deprecated public boolean isGsm();
method public void writeToParcel(android.os.Parcel, int);
field public static final int INVALID = 2147483647; // 0x7fffffff
}
@@ -44605,6 +44797,7 @@
method public String getNumber();
method public int getSimSlotIndex();
method public int getSubscriptionId();
+ method public int getSubscriptionType();
method public boolean isEmbedded();
method public boolean isOpportunistic();
method public void writeToParcel(android.os.Parcel, int);
@@ -44655,6 +44848,8 @@
field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
field public static final int INVALID_SIM_SLOT_INDEX = -1; // 0xffffffff
field public static final int INVALID_SUBSCRIPTION_ID = -1; // 0xffffffff
+ field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0
+ field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1
}
public static class SubscriptionManager.OnOpportunisticSubscriptionsChangedListener {
@@ -45011,7 +45206,9 @@
field public static final int PROTOCOL_IP = 0; // 0x0
field public static final int PROTOCOL_IPV4V6 = 2; // 0x2
field public static final int PROTOCOL_IPV6 = 1; // 0x1
+ field public static final int PROTOCOL_NON_IP = 4; // 0x4
field public static final int PROTOCOL_PPP = 3; // 0x3
+ field public static final int PROTOCOL_UNSTRUCTURED = 5; // 0x5
field public static final int TYPE_CBS = 128; // 0x80
field public static final int TYPE_DEFAULT = 17; // 0x11
field public static final int TYPE_DUN = 8; // 0x8
@@ -49660,6 +49857,7 @@
}
public class Surface implements android.os.Parcelable {
+ ctor public Surface(android.view.SurfaceControl);
ctor public Surface(android.graphics.SurfaceTexture);
method public int describeContents();
method public boolean isValid();
@@ -49682,6 +49880,38 @@
ctor public Surface.OutOfResourcesException(String);
}
+ public final class SurfaceControl implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean isValid();
+ method public void readFromParcel(android.os.Parcel);
+ method public void release();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.SurfaceControl> CREATOR;
+ }
+
+ public static class SurfaceControl.Builder {
+ ctor public SurfaceControl.Builder();
+ method public android.view.SurfaceControl build();
+ method public android.view.SurfaceControl.Builder setBufferSize(@IntRange(from=0) int, @IntRange(from=0) int);
+ method @NonNull public android.view.SurfaceControl.Builder setFormat(int);
+ method public android.view.SurfaceControl.Builder setName(String);
+ method @NonNull public android.view.SurfaceControl.Builder setOpaque(boolean);
+ method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl);
+ }
+
+ public static class SurfaceControl.Transaction implements java.io.Closeable {
+ ctor public SurfaceControl.Transaction();
+ method public void apply();
+ method public void close();
+ method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction);
+ method @NonNull public android.view.SurfaceControl.Transaction reparent(@NonNull android.view.SurfaceControl, @Nullable android.view.SurfaceControl);
+ method @NonNull public android.view.SurfaceControl.Transaction setAlpha(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0, to=1.0) float);
+ method @NonNull public android.view.SurfaceControl.Transaction setBufferSize(@NonNull android.view.SurfaceControl, @IntRange(from=0) int, @IntRange(from=0) int);
+ method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
+ method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
+ method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean);
+ }
+
public interface SurfaceHolder {
method public void addCallback(android.view.SurfaceHolder.Callback);
method public android.view.Surface getSurface();
@@ -49726,6 +49956,7 @@
ctor public SurfaceView(android.content.Context, android.util.AttributeSet, int, int);
method public boolean gatherTransparentRegion(android.graphics.Region);
method public android.view.SurfaceHolder getHolder();
+ method public android.view.SurfaceControl getSurfaceControl();
method public void setSecure(boolean);
method public void setZOrderMediaOverlay(boolean);
method public void setZOrderOnTop(boolean);
@@ -52531,6 +52762,7 @@
public static final class ContentCaptureContext.Builder {
ctor public ContentCaptureContext.Builder();
method public android.view.contentcapture.ContentCaptureContext build();
+ method @NonNull public android.view.contentcapture.ContentCaptureContext.Builder setAction(@NonNull String);
method @NonNull public android.view.contentcapture.ContentCaptureContext.Builder setExtras(@NonNull android.os.Bundle);
method @NonNull public android.view.contentcapture.ContentCaptureContext.Builder setUri(@NonNull android.net.Uri);
}
@@ -52547,12 +52779,12 @@
method @NonNull public final android.view.contentcapture.ContentCaptureSession createContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext);
method public final void destroy();
method public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId();
- method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, int);
- method @NonNull public final android.view.ViewStructure newVirtualViewStructure(@NonNull android.view.autofill.AutofillId, int);
+ method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, long);
+ method @NonNull public final android.view.ViewStructure newVirtualViewStructure(@NonNull android.view.autofill.AutofillId, long);
method public final void notifyViewAppeared(@NonNull android.view.ViewStructure);
method public final void notifyViewDisappeared(@NonNull android.view.autofill.AutofillId);
method public final void notifyViewTextChanged(@NonNull android.view.autofill.AutofillId, @Nullable CharSequence, int);
- method public final void notifyViewsDisappeared(@NonNull android.view.autofill.AutofillId, @NonNull int[]);
+ method public final void notifyViewsDisappeared(@NonNull android.view.autofill.AutofillId, @NonNull long[]);
}
public final class ContentCaptureSessionId implements android.os.Parcelable {
@@ -52891,8 +53123,8 @@
method @Deprecated public boolean isWatchingCursor(android.view.View);
method public void restartInput(android.view.View);
method public void sendAppPrivateCommand(android.view.View, String, android.os.Bundle);
- method public void setAdditionalInputMethodSubtypes(String, android.view.inputmethod.InputMethodSubtype[]);
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype);
+ method @Deprecated public void setAdditionalInputMethodSubtypes(String, android.view.inputmethod.InputMethodSubtype[]);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype);
method @Deprecated public void setInputMethod(android.os.IBinder, String);
method @Deprecated public void setInputMethodAndSubtype(@NonNull android.os.IBinder, String, android.view.inputmethod.InputMethodSubtype);
method @Deprecated public boolean shouldOfferSwitchingToNextInputMethod(android.os.IBinder);
@@ -52910,7 +53142,7 @@
method public void updateCursorAnchorInfo(android.view.View, android.view.inputmethod.CursorAnchorInfo);
method public void updateExtractedText(android.view.View, int, android.view.inputmethod.ExtractedText);
method public void updateSelection(android.view.View, int, int, int, int);
- method public void viewClicked(android.view.View);
+ method @Deprecated public void viewClicked(android.view.View);
field public static final int HIDE_IMPLICIT_ONLY = 1; // 0x1
field public static final int HIDE_NOT_ALWAYS = 2; // 0x2
field public static final int RESULT_HIDDEN = 3; // 0x3
@@ -52989,6 +53221,16 @@
ctor public InspectionCompanion.UninitializedPropertyMapException();
}
+ public final class IntEnumMapping {
+ method @Nullable public String get(int);
+ }
+
+ public static final class IntEnumMapping.Builder {
+ ctor public IntEnumMapping.Builder();
+ method @NonNull public android.view.inspector.IntEnumMapping.Builder addValue(@NonNull String, int);
+ method @NonNull public android.view.inspector.IntEnumMapping build();
+ }
+
public final class IntFlagMapping {
method @NonNull public java.util.Set<java.lang.String> get(int);
}
@@ -53009,7 +53251,7 @@
method public int mapFloat(@NonNull String, @AttrRes int);
method public int mapGravity(@NonNull String, @AttrRes int);
method public int mapInt(@NonNull String, @AttrRes int);
- method public int mapIntEnum(@NonNull String, @AttrRes int, @NonNull android.util.SparseArray<java.lang.String>);
+ method public int mapIntEnum(@NonNull String, @AttrRes int, @NonNull android.view.inspector.IntEnumMapping);
method public int mapIntFlag(@NonNull String, @AttrRes int, @NonNull android.view.inspector.IntFlagMapping);
method public int mapLong(@NonNull String, @AttrRes int);
method public int mapObject(@NonNull String, @AttrRes int);
@@ -56757,7 +56999,7 @@
method public boolean isCursorVisible();
method public boolean isElegantTextHeight();
method public boolean isFallbackLineSpacing();
- method public final boolean isHorizontallyScrolling();
+ method public final boolean isHorizontallyScrollable();
method public boolean isInputMethodTarget();
method public boolean isSingleLine();
method public boolean isSuggestionsEnabled();
diff --git a/api/removed.txt b/api/removed.txt
index 2c567e0..9f4b041 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -239,8 +239,8 @@
}
public class Picture {
- method @Deprecated public static android.graphics.Picture createFromStream(java.io.InputStream);
- method @Deprecated public void writeToStream(java.io.OutputStream);
+ method @Deprecated public static android.graphics.Picture createFromStream(@NonNull java.io.InputStream);
+ method @Deprecated public void writeToStream(@NonNull java.io.OutputStream);
}
@Deprecated public class PixelXorXfermode extends android.graphics.Xfermode {
@@ -325,7 +325,7 @@
@IntDef({0x0, 0xa, 0x14, 0x1e}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NetworkBadging.Badging {
}
- public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
+ @Deprecated public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
method @Deprecated public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(int, android.net.SSLSessionCache);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 2088eaf..315a941 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -244,6 +244,7 @@
}
public static final class R.style {
+ field public static final int Theme_DeviceDefault_DocumentsUI = 16974562; // 0x10302e2
field public static final int Theme_Leanback_FormWizard = 16974544; // 0x10302d0
}
@@ -1095,6 +1096,8 @@
public static final class UsageEvents.Event {
method public int getInstanceId();
method public String getNotificationChannelId();
+ method @Nullable public String getTaskRootClassName();
+ method @Nullable public String getTaskRootPackageName();
field public static final int NOTIFICATION_INTERRUPTION = 12; // 0xc
field public static final int NOTIFICATION_SEEN = 10; // 0xa
field public static final int SLICE_PINNED = 14; // 0xe
@@ -1109,6 +1112,8 @@
public final class UsageStatsManager {
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets();
+ method public int getUsageSource();
+ method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void registerAppUsageLimitObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerAppUsageObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerUsageSessionObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent, @Nullable android.app.PendingIntent);
method public void reportUsageStart(@NonNull android.app.Activity, @NonNull String);
@@ -1116,6 +1121,7 @@
method public void reportUsageStop(@NonNull android.app.Activity, @NonNull String);
method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBucket(String, int);
method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBuckets(java.util.Map<java.lang.String,java.lang.Integer>);
+ method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void unregisterAppUsageLimitObserver(int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterAppUsageObserver(int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterUsageSessionObserver(int);
method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(String, long, android.os.UserHandle);
@@ -1124,6 +1130,8 @@
field public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED";
field public static final int STANDBY_BUCKET_EXEMPTED = 5; // 0x5
field public static final int STANDBY_BUCKET_NEVER = 50; // 0x32
+ field public static final int USAGE_SOURCE_CURRENT_ACTIVITY = 2; // 0x2
+ field public static final int USAGE_SOURCE_TASK_ROOT_ACTIVITY = 1; // 0x1
}
}
@@ -1136,19 +1144,46 @@
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean enableNoAutoConnect();
method public boolean isBleScanAlwaysAvailable();
method public boolean isLeEnabled();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean registerMetadataListener(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothAdapter.MetadataListener, android.os.Handler);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean unregisterMetadataListener(android.bluetooth.BluetoothDevice);
field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED";
field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE";
}
+ public abstract static class BluetoothAdapter.MetadataListener {
+ ctor public BluetoothAdapter.MetadataListener();
+ method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String);
+ }
+
public final class BluetoothDevice implements android.os.Parcelable {
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public String getMetadata(int);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, String);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int);
field public static final int ACCESS_ALLOWED = 1; // 0x1
field public static final int ACCESS_REJECTED = 2; // 0x2
field public static final int ACCESS_UNKNOWN = 0; // 0x0
+ field public static final int METADATA_COMPANION_APP = 4; // 0x4
+ field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10
+ field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3
+ field public static final int METADATA_IS_UNTHETHERED_HEADSET = 6; // 0x6
+ field public static final int METADATA_MAIN_ICON = 5; // 0x5
+ field public static final int METADATA_MANUFACTURER_NAME = 0; // 0x0
+ field public static final int METADATA_MAX_LENGTH = 2048; // 0x800
+ field public static final int METADATA_MODEL_NAME = 1; // 0x1
+ field public static final int METADATA_SOFTWARE_VERSION = 2; // 0x2
+ field public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12; // 0xc
+ field public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15; // 0xf
+ field public static final int METADATA_UNTHETHERED_CASE_ICON = 9; // 0x9
+ field public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10; // 0xa
+ field public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13; // 0xd
+ field public static final int METADATA_UNTHETHERED_LEFT_ICON = 7; // 0x7
+ field public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11; // 0xb
+ field public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14; // 0xe
+ field public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8; // 0x8
}
public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
@@ -1322,6 +1357,7 @@
public class OverlayManager {
method public java.util.List<android.content.om.OverlayInfo> getOverlayInfosForTarget(@Nullable String, int);
+ method public boolean setEnabled(@Nullable String, boolean, int);
method public boolean setEnabledExclusiveInCategory(@Nullable String, int);
}
@@ -1644,22 +1680,17 @@
package android.content.rollback {
public final class PackageRollbackInfo implements android.os.Parcelable {
- ctor public PackageRollbackInfo(String, android.content.rollback.PackageRollbackInfo.PackageVersion, android.content.rollback.PackageRollbackInfo.PackageVersion);
method public int describeContents();
+ method public String getPackageName();
+ method public android.content.pm.VersionedPackage getVersionRolledBackFrom();
+ method public android.content.pm.VersionedPackage getVersionRolledBackTo();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.rollback.PackageRollbackInfo> CREATOR;
- field public final android.content.rollback.PackageRollbackInfo.PackageVersion higherVersion;
- field public final android.content.rollback.PackageRollbackInfo.PackageVersion lowerVersion;
- field public final String packageName;
- }
-
- public static class PackageRollbackInfo.PackageVersion {
- ctor public PackageRollbackInfo.PackageVersion(long);
- field public final long versionCode;
}
public final class RollbackInfo implements android.os.Parcelable {
method public int describeContents();
+ method public int getRollbackId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.rollback.RollbackInfo> CREATOR;
field public final android.content.rollback.PackageRollbackInfo targetPackage;
@@ -1776,8 +1807,13 @@
}
public final class ColorDisplayManager {
+ method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public int getTransformCapabilities();
method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setAppSaturationLevel(@NonNull String, @IntRange(from=0, to=100) int);
method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setSaturationLevel(@IntRange(from=0, to=100) int);
+ field public static final int CAPABILITY_HARDWARE_ACCELERATION_GLOBAL = 2; // 0x2
+ field public static final int CAPABILITY_HARDWARE_ACCELERATION_PER_APP = 4; // 0x4
+ field public static final int CAPABILITY_NONE = 0; // 0x0
+ field public static final int CAPABILITY_PROTECTED_CONTENT = 1; // 0x1
}
public final class DisplayManager {
@@ -3204,12 +3240,14 @@
method public int getQuality();
method public float getSmallestDisplacement();
method public android.os.WorkSource getWorkSource();
+ method public boolean isLocationSettingsIgnored();
method public boolean isLowPowerMode();
method public android.location.LocationRequest setExpireAt(long);
method public android.location.LocationRequest setExpireIn(long);
method public android.location.LocationRequest setFastestInterval(long);
method public void setHideFromAppOps(boolean);
method public android.location.LocationRequest setInterval(long);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest setLocationSettingsIgnored(boolean);
method public android.location.LocationRequest setLowPowerMode(boolean);
method public android.location.LocationRequest setNumUpdates(int);
method public android.location.LocationRequest setProvider(String);
@@ -3359,6 +3397,15 @@
method @NonNull public android.media.TimedMetaData.Builder setTimedMetaData(long, @NonNull byte[]);
}
+ public abstract class VolumeProvider {
+ method public void setCallback(android.media.VolumeProvider.Callback);
+ }
+
+ public abstract static class VolumeProvider.Callback {
+ ctor public VolumeProvider.Callback();
+ method public abstract void onVolumeChanged(android.media.VolumeProvider);
+ }
+
}
package android.media.audiopolicy {
@@ -3530,6 +3577,80 @@
method public void unregisterCallback(@NonNull android.media.session.ControllerCallbackLink);
}
+ public abstract static class MediaSession.Callback {
+ method public void onSetMediaButtonEventDelegate(@NonNull android.media.session.MediaSessionEngine.MediaButtonEventDelegate);
+ }
+
+ public static final class MediaSession.Token implements android.os.Parcelable {
+ method public android.media.session.ControllerLink getControllerLink();
+ }
+
+ public final class MediaSessionEngine implements java.lang.AutoCloseable {
+ ctor public MediaSessionEngine(@NonNull android.content.Context, @NonNull android.media.session.SessionLink, @NonNull android.media.session.SessionCallbackLink, @NonNull android.media.session.MediaSessionEngine.CallbackStub, int);
+ method public void close();
+ method public String getCallingPackage();
+ method @NonNull public android.media.session.MediaController getController();
+ method @NonNull public android.media.session.MediaSessionManager.RemoteUserInfo getCurrentControllerInfo();
+ method @NonNull public android.media.session.MediaSession.Token getSessionToken();
+ method public boolean isActive();
+ method public static boolean isActiveState(int);
+ method public void sendSessionEvent(@NonNull String, @Nullable android.os.Bundle);
+ method public void setActive(boolean);
+ method public void setCallback(@Nullable android.media.session.MediaSession.Callback);
+ method public void setCallback(@Nullable android.media.session.MediaSession.Callback, @NonNull android.os.Handler);
+ method public void setExtras(@Nullable android.os.Bundle);
+ method public void setFlags(int);
+ method public void setMediaButtonReceiver(@Nullable android.app.PendingIntent);
+ method public void setMetadata(@Nullable android.media.MediaMetadata);
+ method public void setPlaybackState(@Nullable android.media.session.PlaybackState);
+ method public void setPlaybackToLocal(android.media.AudioAttributes);
+ method public void setPlaybackToRemote(@NonNull android.media.VolumeProvider);
+ method public void setQueue(@Nullable java.util.List<android.media.session.MediaSession.QueueItem>);
+ method public void setQueueTitle(@Nullable CharSequence);
+ method public void setRatingType(int);
+ method public void setSessionActivity(@Nullable android.app.PendingIntent);
+ }
+
+ public static final class MediaSessionEngine.CallbackStub {
+ ctor public MediaSessionEngine.CallbackStub();
+ method public void onAdjustVolume(String, int, int, android.media.session.ControllerCallbackLink, int);
+ method public void onCommand(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle, android.os.ResultReceiver);
+ method public void onCustomAction(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+ method public void onFastForward(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onMediaButton(String, int, int, android.content.Intent, int, android.os.ResultReceiver);
+ method public void onMediaButtonFromController(String, int, int, android.media.session.ControllerCallbackLink, android.content.Intent);
+ method public void onNext(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onPause(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onPlay(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onPlayFromMediaId(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+ method public void onPlayFromSearch(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+ method public void onPlayFromUri(String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle);
+ method public void onPrepare(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onPrepareFromMediaId(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+ method public void onPrepareFromSearch(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+ method public void onPrepareFromUri(String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle);
+ method public void onPrevious(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onRate(String, int, int, android.media.session.ControllerCallbackLink, android.media.Rating);
+ method public void onRewind(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onSeekTo(String, int, int, android.media.session.ControllerCallbackLink, long);
+ method public void onSetVolumeTo(String, int, int, android.media.session.ControllerCallbackLink, int);
+ method public void onSkipToTrack(String, int, int, android.media.session.ControllerCallbackLink, long);
+ method public void onStop(String, int, int, android.media.session.ControllerCallbackLink);
+ }
+
+ public static interface MediaSessionEngine.MediaButtonEventDelegate {
+ method public boolean onMediaButtonIntent(android.content.Intent);
+ }
+
+ public static final class MediaSessionEngine.QueueItem {
+ ctor public MediaSessionEngine.QueueItem(android.media.MediaDescription, long);
+ ctor public MediaSessionEngine.QueueItem(android.os.Parcel);
+ method public android.media.MediaDescription getDescription();
+ method public long getQueueId();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int UNKNOWN_ID = -1; // 0xffffffff
+ }
+
public final class MediaSessionManager {
method @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, @Nullable android.os.Handler);
method @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER) public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, @Nullable android.os.Handler);
@@ -3895,12 +4016,16 @@
package android.net {
public class CaptivePortal implements android.os.Parcelable {
+ ctor public CaptivePortal(android.os.IBinder);
+ method public void useNetwork();
field public static final int APP_RETURN_DISMISSED = 0; // 0x0
field public static final int APP_RETURN_UNWANTED = 1; // 0x1
field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
}
public class ConnectivityManager {
+ method @RequiresPermission("android.permission.PACKET_KEEPALIVE_OFFLOAD") public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull java.io.FileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
+ method public boolean getAvoidBadWifi();
method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl();
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void setAirplaneMode(boolean);
@@ -3920,6 +4045,10 @@
method public void onTetheringStarted();
}
+ public final class IpPrefix implements android.os.Parcelable {
+ ctor public IpPrefix(java.net.InetAddress, int);
+ }
+
public final class IpSecManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void applyTunnelModeTransform(@NonNull android.net.IpSecManager.IpSecTunnelInterface, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(@NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
@@ -3952,6 +4081,7 @@
}
public class LinkAddress implements android.os.Parcelable {
+ ctor public LinkAddress(java.net.InetAddress, int, int, int);
ctor public LinkAddress(java.net.InetAddress, int);
ctor public LinkAddress(String);
method public boolean isGlobalPreferred();
@@ -3962,14 +4092,35 @@
public final class LinkProperties implements android.os.Parcelable {
ctor public LinkProperties();
+ ctor public LinkProperties(android.net.LinkProperties);
+ method public boolean addDnsServer(java.net.InetAddress);
method public boolean addRoute(android.net.RouteInfo);
method public void clear();
+ method @Nullable public android.net.IpPrefix getNat64Prefix();
+ method public java.util.List<java.net.InetAddress> getPcscfServers();
+ method public String getTcpBufferSizes();
+ method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
+ method public boolean hasGlobalIPv6Address();
+ method public boolean hasIPv4Address();
+ method public boolean hasIPv6DefaultRoute();
+ method public boolean isIPv4Provisioned();
+ method public boolean isIPv6Provisioned();
+ method public boolean isProvisioned();
+ method public boolean isReachable(java.net.InetAddress);
+ method public boolean removeDnsServer(java.net.InetAddress);
+ method public boolean removeRoute(android.net.RouteInfo);
method public void setDnsServers(java.util.Collection<java.net.InetAddress>);
method public void setDomains(String);
method public void setHttpProxy(android.net.ProxyInfo);
method public void setInterfaceName(String);
method public void setLinkAddresses(java.util.Collection<android.net.LinkAddress>);
method public void setMtu(int);
+ method public void setNat64Prefix(android.net.IpPrefix);
+ method public void setPcscfServers(java.util.Collection<java.net.InetAddress>);
+ method public void setPrivateDnsServerName(@Nullable String);
+ method public void setTcpBufferSizes(String);
+ method public void setUsePrivateDns(boolean);
+ method public void setValidatedPrivateDnsServers(java.util.Collection<java.net.InetAddress>);
}
public class Network implements android.os.Parcelable {
@@ -3978,6 +4129,8 @@
public final class NetworkCapabilities implements android.os.Parcelable {
method public int getSignalStrength();
+ method public int[] getTransportTypes();
+ method public boolean satisfiedByNetworkCapabilities(android.net.NetworkCapabilities);
field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
}
@@ -4018,6 +4171,7 @@
}
public final class RouteInfo implements android.os.Parcelable {
+ ctor public RouteInfo(android.net.IpPrefix, java.net.InetAddress, String, int);
method public int getType();
field public static final int RTN_THROW = 9; // 0x9
field public static final int RTN_UNICAST = 1; // 0x1
@@ -4151,8 +4305,10 @@
}
public class IpConnectivityLog {
+ ctor public IpConnectivityLog();
method public boolean log(long, android.net.metrics.IpConnectivityLog.Event);
method public boolean log(String, android.net.metrics.IpConnectivityLog.Event);
+ method public boolean log(android.net.Network, int[], android.net.metrics.IpConnectivityLog.Event);
method public boolean log(int, int[], android.net.metrics.IpConnectivityLog.Event);
method public boolean log(android.net.metrics.IpConnectivityLog.Event);
}
@@ -4198,6 +4354,20 @@
field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3
}
+ public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event {
+ }
+
+ public static class RaEvent.Builder {
+ ctor public RaEvent.Builder();
+ method public android.net.metrics.RaEvent build();
+ method public android.net.metrics.RaEvent.Builder updateDnsslLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updatePrefixPreferredLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updatePrefixValidLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updateRdnssLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updateRouteInfoLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updateRouterLifetime(long);
+ }
+
public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
method public static String getProbeName(int);
field public static final int DNS_FAILURE = 0; // 0x0
@@ -4220,6 +4390,17 @@
}
+package android.net.util {
+
+ public class SocketUtils {
+ method public static void bindSocketToInterface(java.io.FileDescriptor, String) throws android.system.ErrnoException;
+ method public static java.net.SocketAddress makeNetlinkSocketAddress(int, int);
+ method public static java.net.SocketAddress makePacketSocketAddress(short, int);
+ method public static java.net.SocketAddress makePacketSocketAddress(int, byte[]);
+ }
+
+}
+
package android.net.wifi {
public abstract class EasyConnectStatusCallback {
@@ -4458,10 +4639,10 @@
}
public class WifiManager {
+ method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void addWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.WifiUsabilityStatsListener);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void connect(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void connect(int, android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void disable(int, android.net.wifi.WifiManager.ActionListener);
- method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void disableEphemeralNetwork(String);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void forget(int, android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(java.util.List<android.net.wifi.ScanResult>);
@@ -4474,6 +4655,7 @@
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled();
method public boolean isWifiScannerSupported();
method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void registerNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback, @Nullable android.os.Handler);
+ method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void removeWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.WifiUsabilityStatsListener);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void save(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission("android.permission.WIFI_SET_DEVICE_MOBILITY_STATE") public void setDeviceMobilityState(int);
method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
@@ -4531,6 +4713,10 @@
method public void select(@NonNull android.net.wifi.WifiConfiguration);
}
+ public static interface WifiManager.WifiUsabilityStatsListener {
+ method public void onStatsUpdated(int, boolean, android.net.wifi.WifiUsabilityStatsEntry);
+ }
+
public class WifiNetworkConnectionStatistics implements android.os.Parcelable {
ctor public WifiNetworkConnectionStatistics(int, int);
ctor public WifiNetworkConnectionStatistics();
@@ -4660,6 +4846,31 @@
field @Deprecated public int unchangedSampleSize;
}
+ public final class WifiUsabilityStatsEntry implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.net.wifi.WifiUsabilityStatsEntry> CREATOR;
+ field public final int linkSpeedMbps;
+ field public final int rssi;
+ field public final long timeStampMs;
+ field public final long totalBackgroundScanTimeMs;
+ field public final long totalBeaconRx;
+ field public final long totalCcaBusyFreqTimeMs;
+ field public final long totalHotspot2ScanTimeMs;
+ field public final long totalNanScanTimeMs;
+ field public final long totalPnoScanTimeMs;
+ field public final long totalRadioOnFreqTimeMs;
+ field public final long totalRadioOnTimeMs;
+ field public final long totalRadioRxTimeMs;
+ field public final long totalRadioTxTimeMs;
+ field public final long totalRoamScanTimeMs;
+ field public final long totalRxSuccess;
+ field public final long totalScanTimeMs;
+ field public final long totalTxBad;
+ field public final long totalTxRetries;
+ field public final long totalTxSuccess;
+ }
+
}
package android.net.wifi.aware {
@@ -4695,7 +4906,7 @@
method public abstract void onProvisioningStatus(int);
field public static final int OSU_FAILURE_ADD_PASSPOINT_CONFIGURATION = 22; // 0x16
field public static final int OSU_FAILURE_AP_CONNECTION = 1; // 0x1
- field public static final int OSU_FAILURE_INVALID_SERVER_URL = 8; // 0x8
+ field public static final int OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU = 8; // 0x8
field public static final int OSU_FAILURE_NO_AAA_SERVER_TRUST_ROOT_NODE = 17; // 0x11
field public static final int OSU_FAILURE_NO_AAA_TRUST_ROOT_CERTIFICATE = 21; // 0x15
field public static final int OSU_FAILURE_NO_OSU_ACTIVITY_FOUND = 14; // 0xe
@@ -5053,6 +5264,12 @@
method public void onResult(android.os.Bundle);
}
+ public class ServiceSpecificException extends java.lang.RuntimeException {
+ ctor public ServiceSpecificException(int, String);
+ ctor public ServiceSpecificException(int);
+ field public final int errorCode;
+ }
+
public final class StatsDimensionsValue implements android.os.Parcelable {
method public int describeContents();
method public boolean getBooleanValue();
@@ -5406,12 +5623,31 @@
field public static final String NAMESPACE_GAME_DRIVER = "game_driver";
field public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
field public static final String NAMESPACE_NETD_NATIVE = "netd_native";
+ field public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant";
+ }
+
+ public static interface DeviceConfig.FsiBoot {
+ field public static final String NAMESPACE = "fsi_boot";
+ field public static final String OOB_ENABLED = "oob_enabled";
+ field public static final String OOB_WHITELIST = "oob_whitelist";
+ }
+
+ public static interface DeviceConfig.IntelligenceAttention {
+ field public static final String NAMESPACE = "intelligence_attention";
+ field public static final String PROPERTY_ATTENTION_CHECK_ENABLED = "attention_check_enabled";
+ field public static final String PROPERTY_ATTENTION_CHECK_SETTINGS = "attention_check_settings";
}
public static interface DeviceConfig.OnPropertyChangedListener {
method public void onPropertyChanged(String, String, String);
}
+ public static interface DeviceConfig.Telephony {
+ field public static final String NAMESPACE = "telephony";
+ field public static final String PROPERTY_ENABLE_RAMPING_RINGER = "enable_ramping_ringer";
+ field public static final String PROPERTY_RAMPING_RINGER_DURATION = "ramping_duration";
+ }
+
public final class DocumentsContract {
method public static boolean isManageMode(android.net.Uri);
method public static android.net.Uri setManageMode(android.net.Uri);
@@ -5593,6 +5829,7 @@
field public static final String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS = "lock_screen_allow_private_notifications";
field public static final String LOCK_SCREEN_SHOW_NOTIFICATIONS = "lock_screen_show_notifications";
field public static final String MANUAL_RINGER_TOGGLE_COUNT = "manual_ringer_toggle_count";
+ field public static final String THEME_CUSTOMIZATION_OVERLAY_PACKAGES = "theme_customization_overlay_packages";
field public static final String USER_SETUP_COMPLETE = "user_setup_complete";
field public static final int USER_SETUP_PERSONALIZATION_COMPLETE = 10; // 0xa
field public static final int USER_SETUP_PERSONALIZATION_NOT_STARTED = 0; // 0x0
@@ -5868,10 +6105,7 @@
method public int getTaskId();
}
- public final class FillResponse implements android.os.Parcelable {
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.autofill.augmented.FillResponse> CREATOR;
+ public final class FillResponse {
}
public static final class FillResponse.Builder {
@@ -5885,7 +6119,6 @@
ctor public FillWindow();
method public void destroy();
method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long);
- field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L
}
public abstract class PresentationParams {
@@ -6047,12 +6280,15 @@
method public abstract int onSwitchToSubscription(int, @Nullable String, boolean);
method public abstract int onUpdateSubscriptionNickname(int, String, String);
field public static final String ACTION_BIND_CARRIER_PROVISIONING_SERVICE = "android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE";
+ field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+ field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
field @Deprecated public static final String ACTION_RESOLVE_CONFIRMATION_CODE = "android.service.euicc.action.RESOLVE_CONFIRMATION_CODE";
field public static final String ACTION_RESOLVE_DEACTIVATE_SIM = "android.service.euicc.action.RESOLVE_DEACTIVATE_SIM";
field public static final String ACTION_RESOLVE_NO_PRIVILEGES = "android.service.euicc.action.RESOLVE_NO_PRIVILEGES";
field public static final String ACTION_RESOLVE_RESOLVABLE_ERRORS = "android.service.euicc.action.RESOLVE_RESOLVABLE_ERRORS";
+ field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
field public static final String CATEGORY_EUICC_UI = "android.service.euicc.category.EUICC_UI";
field public static final String EUICC_SERVICE_INTERFACE = "android.service.euicc.EuiccService";
field public static final String EXTRA_RESOLUTION_ALLOW_POLICY_RULES = "android.service.euicc.extra.RESOLUTION_ALLOW_POLICY_RULES";
@@ -6108,77 +6344,6 @@
package android.service.notification {
- public final class Adjustment implements android.os.Parcelable {
- ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int);
- ctor protected Adjustment(android.os.Parcel);
- method public int describeContents();
- method public CharSequence getExplanation();
- method public String getKey();
- method public String getPackage();
- method public android.os.Bundle getSignals();
- method public int getUser();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
- field public static final String KEY_IMPORTANCE = "key_importance";
- field public static final String KEY_PEOPLE = "key_people";
- field public static final String KEY_SMART_ACTIONS = "key_smart_actions";
- field public static final String KEY_SMART_REPLIES = "key_smart_replies";
- field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
- field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
- }
-
- public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
- ctor public NotificationAssistantService();
- method public final void adjustNotification(android.service.notification.Adjustment);
- method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
- method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
- method public final android.os.IBinder onBind(android.content.Intent);
- method public void onNotificationDirectReplied(@NonNull String);
- method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
- method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
- method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
- method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
- method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, String);
- method public void onNotificationsSeen(java.util.List<java.lang.String>);
- method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
- method public final void unsnoozeNotification(String);
- field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
- field public static final int SOURCE_FROM_APP = 0; // 0x0
- field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1
- }
-
- public final class NotificationStats implements android.os.Parcelable {
- ctor public NotificationStats();
- ctor protected NotificationStats(android.os.Parcel);
- method public int describeContents();
- method public int getDismissalSentiment();
- method public int getDismissalSurface();
- method public boolean hasDirectReplied();
- method public boolean hasExpanded();
- method public boolean hasInteracted();
- method public boolean hasSeen();
- method public boolean hasSnoozed();
- method public boolean hasViewedSettings();
- method public void setDirectReplied();
- method public void setDismissalSentiment(int);
- method public void setDismissalSurface(int);
- method public void setExpanded();
- method public void setSeen();
- method public void setSnoozed();
- method public void setViewedSettings();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
- field public static final int DISMISSAL_AOD = 2; // 0x2
- field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
- field public static final int DISMISSAL_OTHER = 0; // 0x0
- field public static final int DISMISSAL_PEEK = 1; // 0x1
- field public static final int DISMISSAL_SHADE = 3; // 0x3
- field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
- field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
- field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
- field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
- }
-
public final class SnoozeCriterion implements android.os.Parcelable {
ctor public SnoozeCriterion(String, CharSequence, CharSequence);
ctor protected SnoozeCriterion(android.os.Parcel);
@@ -6630,6 +6795,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.WRITE_SECURE_SETTINGS}) public boolean setDefaultDialer(String);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(android.telecom.PhoneAccountHandle);
field public static final String EXTRA_CALL_BACK_INTENT = "android.telecom.extra.CALL_BACK_INTENT";
field public static final String EXTRA_CLEAR_MISSED_CALLS_INTENT = "android.telecom.extra.CLEAR_MISSED_CALLS_INTENT";
field public static final String EXTRA_CONNECTION_SERVICE = "android.telecom.extra.CONNECTION_SERVICE";
@@ -6714,42 +6880,211 @@
}
public final class DataFailCause {
+ field public static final int ACCESS_ATTEMPT_ALREADY_IN_PROGRESS = 2219; // 0x8ab
+ field public static final int ACCESS_BLOCK = 2087; // 0x827
+ field public static final int ACCESS_BLOCK_ALL = 2088; // 0x828
+ field public static final int ACCESS_CLASS_DSAC_REJECTION = 2108; // 0x83c
+ field public static final int ACCESS_CONTROL_LIST_CHECK_FAILURE = 2128; // 0x850
+ field public static final int ACTIVATION_REJECTED_BCM_VIOLATION = 48; // 0x30
field public static final int ACTIVATION_REJECT_GGSN = 30; // 0x1e
field public static final int ACTIVATION_REJECT_UNSPECIFIED = 31; // 0x1f
field public static final int ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED = 65; // 0x41
+ field public static final int APN_DISABLED = 2045; // 0x7fd
+ field public static final int APN_DISALLOWED_ON_ROAMING = 2059; // 0x80b
+ field public static final int APN_MISMATCH = 2054; // 0x806
+ field public static final int APN_PARAMETERS_CHANGED = 2060; // 0x80c
+ field public static final int APN_PENDING_HANDOVER = 2041; // 0x7f9
field public static final int APN_TYPE_CONFLICT = 112; // 0x70
field public static final int AUTH_FAILURE_ON_EMERGENCY_CALL = 122; // 0x7a
+ field public static final int BEARER_HANDLING_NOT_SUPPORTED = 60; // 0x3c
+ field public static final int CALL_DISALLOWED_IN_ROAMING = 2068; // 0x814
+ field public static final int CALL_PREEMPT_BY_EMERGENCY_APN = 127; // 0x7f
+ field public static final int CANNOT_ENCODE_OTA_MESSAGE = 2159; // 0x86f
+ field public static final int CDMA_ALERT_STOP = 2077; // 0x81d
+ field public static final int CDMA_INCOMING_CALL = 2076; // 0x81c
+ field public static final int CDMA_INTERCEPT = 2073; // 0x819
+ field public static final int CDMA_LOCK = 2072; // 0x818
+ field public static final int CDMA_RELEASE_DUE_TO_SO_REJECTION = 2075; // 0x81b
+ field public static final int CDMA_REORDER = 2074; // 0x81a
+ field public static final int CDMA_RETRY_ORDER = 2086; // 0x826
+ field public static final int CHANNEL_ACQUISITION_FAILURE = 2078; // 0x81e
+ field public static final int CLOSE_IN_PROGRESS = 2030; // 0x7ee
+ field public static final int COLLISION_WITH_NETWORK_INITIATED_REQUEST = 56; // 0x38
field public static final int COMPANION_IFACE_IN_USE = 118; // 0x76
+ field public static final int CONCURRENT_SERVICES_INCOMPATIBLE = 2083; // 0x823
+ field public static final int CONCURRENT_SERVICES_NOT_ALLOWED = 2091; // 0x82b
+ field public static final int CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION = 2080; // 0x820
field public static final int CONDITIONAL_IE_ERROR = 100; // 0x64
+ field public static final int CONGESTION = 2106; // 0x83a
+ field public static final int CONNECTION_RELEASED = 2113; // 0x841
+ field public static final int CS_DOMAIN_NOT_AVAILABLE = 2181; // 0x885
+ field public static final int CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED = 2188; // 0x88c
+ field public static final int DATA_PLAN_EXPIRED = 2198; // 0x896
+ field public static final int DATA_ROAMING_SETTINGS_DISABLED = 2064; // 0x810
+ field public static final int DATA_SETTINGS_DISABLED = 2063; // 0x80f
+ field public static final int DBM_OR_SMS_IN_PROGRESS = 2211; // 0x8a3
+ field public static final int DDS_SWITCHED = 2065; // 0x811
+ field public static final int DDS_SWITCH_IN_PROGRESS = 2067; // 0x813
+ field public static final int DRB_RELEASED_BY_RRC = 2112; // 0x840
+ field public static final int DS_EXPLICIT_DEACTIVATION = 2125; // 0x84d
+ field public static final int DUAL_SWITCH = 2227; // 0x8b3
+ field public static final int DUN_CALL_DISALLOWED = 2056; // 0x808
+ field public static final int DUPLICATE_BEARER_ID = 2118; // 0x846
+ field public static final int EHRPD_TO_HRPD_FALLBACK = 2049; // 0x801
+ field public static final int EMBMS_NOT_ENABLED = 2193; // 0x891
+ field public static final int EMBMS_REGULAR_DEACTIVATION = 2195; // 0x893
field public static final int EMERGENCY_IFACE_ONLY = 116; // 0x74
+ field public static final int EMERGENCY_MODE = 2221; // 0x8ad
field public static final int EMM_ACCESS_BARRED = 115; // 0x73
field public static final int EMM_ACCESS_BARRED_INFINITE_RETRY = 121; // 0x79
+ field public static final int EMM_ATTACH_FAILED = 2115; // 0x843
+ field public static final int EMM_ATTACH_STARTED = 2116; // 0x844
+ field public static final int EMM_DETACHED = 2114; // 0x842
+ field public static final int EMM_T3417_EXPIRED = 2130; // 0x852
+ field public static final int EMM_T3417_EXT_EXPIRED = 2131; // 0x853
+ field public static final int EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED = 2178; // 0x882
+ field public static final int EPS_SERVICES_NOT_ALLOWED_IN_PLMN = 2179; // 0x883
field public static final int ERROR_UNSPECIFIED = 65535; // 0xffff
+ field public static final int ESM_BAD_OTA_MESSAGE = 2122; // 0x84a
+ field public static final int ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK = 2120; // 0x848
+ field public static final int ESM_COLLISION_SCENARIOS = 2119; // 0x847
+ field public static final int ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT = 2124; // 0x84c
+ field public static final int ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL = 2123; // 0x84b
+ field public static final int ESM_FAILURE = 2182; // 0x886
field public static final int ESM_INFO_NOT_RECEIVED = 53; // 0x35
+ field public static final int ESM_LOCAL_CAUSE_NONE = 2126; // 0x84e
+ field public static final int ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER = 2121; // 0x849
+ field public static final int ESM_PROCEDURE_TIME_OUT = 2155; // 0x86b
+ field public static final int ESM_UNKNOWN_EPS_BEARER_CONTEXT = 2111; // 0x83f
+ field public static final int EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE = 2201; // 0x899
+ field public static final int EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY = 2200; // 0x898
+ field public static final int EVDO_HDR_CHANGED = 2202; // 0x89a
+ field public static final int EVDO_HDR_CONNECTION_SETUP_TIMEOUT = 2206; // 0x89e
+ field public static final int EVDO_HDR_EXITED = 2203; // 0x89b
+ field public static final int EVDO_HDR_NO_SESSION = 2204; // 0x89c
+ field public static final int EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL = 2205; // 0x89d
+ field public static final int FADE = 2217; // 0x8a9
+ field public static final int FAILED_TO_ACQUIRE_COLOCATED_HDR = 2207; // 0x89f
field public static final int FEATURE_NOT_SUPP = 40; // 0x28
field public static final int FILTER_SEMANTIC_ERROR = 44; // 0x2c
field public static final int FILTER_SYTAX_ERROR = 45; // 0x2d
+ field public static final int FORBIDDEN_APN_NAME = 2066; // 0x812
field public static final int GPRS_REGISTRATION_FAIL = -2; // 0xfffffffe
+ field public static final int GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED = 2097; // 0x831
+ field public static final int GPRS_SERVICES_NOT_ALLOWED = 2098; // 0x832
+ field public static final int GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN = 2103; // 0x837
+ field public static final int HANDOFF_PREFERENCE_CHANGED = 2251; // 0x8cb
+ field public static final int HDR_ACCESS_FAILURE = 2213; // 0x8a5
+ field public static final int HDR_FADE = 2212; // 0x8a4
+ field public static final int HDR_NO_LOCK_GRANTED = 2210; // 0x8a2
field public static final int IFACE_AND_POL_FAMILY_MISMATCH = 120; // 0x78
field public static final int IFACE_MISMATCH = 117; // 0x75
+ field public static final int ILLEGAL_ME = 2096; // 0x830
+ field public static final int ILLEGAL_MS = 2095; // 0x82f
+ field public static final int IMEI_NOT_ACCEPTED = 2177; // 0x881
+ field public static final int IMPLICITLY_DETACHED = 2100; // 0x834
+ field public static final int IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER = 2176; // 0x880
+ field public static final int INCOMING_CALL_REJECTED = 2092; // 0x82c
field public static final int INSUFFICIENT_RESOURCES = 26; // 0x1a
+ field public static final int INTERFACE_IN_USE = 2058; // 0x80a
field public static final int INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN = 114; // 0x72
+ field public static final int INTERNAL_EPC_NONEPC_TRANSITION = 2057; // 0x809
+ field public static final int INVALID_CONNECTION_ID = 2156; // 0x86c
+ field public static final int INVALID_DNS_ADDR = 123; // 0x7b
+ field public static final int INVALID_EMM_STATE = 2190; // 0x88e
field public static final int INVALID_MANDATORY_INFO = 96; // 0x60
+ field public static final int INVALID_MODE = 2223; // 0x8af
field public static final int INVALID_PCSCF_ADDR = 113; // 0x71
+ field public static final int INVALID_PCSCF_OR_DNS_ADDRESS = 124; // 0x7c
+ field public static final int INVALID_PRIMARY_NSAPI = 2158; // 0x86e
+ field public static final int INVALID_SIM_STATE = 2224; // 0x8b0
field public static final int INVALID_TRANSACTION_ID = 81; // 0x51
+ field public static final int IPV6_ADDRESS_TRANSFER_FAILED = 2047; // 0x7ff
+ field public static final int IPV6_PREFIX_UNAVAILABLE = 2250; // 0x8ca
field public static final int IP_ADDRESS_MISMATCH = 119; // 0x77
+ field public static final int IP_VERSION_MISMATCH = 2055; // 0x807
+ field public static final int IRAT_HANDOVER_FAILED = 2194; // 0x892
+ field public static final int IS707B_MAX_ACCESS_PROBES = 2089; // 0x829
+ field public static final int LIMITED_TO_IPV4 = 2234; // 0x8ba
+ field public static final int LIMITED_TO_IPV6 = 2235; // 0x8bb
field public static final int LLC_SNDCP = 25; // 0x19
+ field public static final int LOCAL_END = 2215; // 0x8a7
+ field public static final int LOCATION_AREA_NOT_ALLOWED = 2102; // 0x836
field public static final int LOST_CONNECTION = 65540; // 0x10004
+ field public static final int LOWER_LAYER_REGISTRATION_FAILURE = 2197; // 0x895
+ field public static final int LOW_POWER_MODE_OR_POWERING_DOWN = 2044; // 0x7fc
+ field public static final int LTE_NAS_SERVICE_REQUEST_FAILED = 2117; // 0x845
+ field public static final int LTE_THROTTLING_NOT_REQUIRED = 2127; // 0x84f
+ field public static final int MAC_FAILURE = 2183; // 0x887
+ field public static final int MAXIMIUM_NSAPIS_EXCEEDED = 2157; // 0x86d
+ field public static final int MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED = 2166; // 0x876
+ field public static final int MAX_ACCESS_PROBE = 2079; // 0x81f
+ field public static final int MAX_IPV4_CONNECTIONS = 2052; // 0x804
+ field public static final int MAX_IPV6_CONNECTIONS = 2053; // 0x805
+ field public static final int MAX_PPP_INACTIVITY_TIMER_EXPIRED = 2046; // 0x7fe
field public static final int MESSAGE_INCORRECT_SEMANTIC = 95; // 0x5f
field public static final int MESSAGE_TYPE_UNSUPPORTED = 97; // 0x61
+ field public static final int MIP_CONFIG_FAILURE = 2050; // 0x802
+ field public static final int MIP_FA_ADMIN_PROHIBITED = 2001; // 0x7d1
+ field public static final int MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED = 2012; // 0x7dc
+ field public static final int MIP_FA_ENCAPSULATION_UNAVAILABLE = 2008; // 0x7d8
+ field public static final int MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE = 2004; // 0x7d4
+ field public static final int MIP_FA_INSUFFICIENT_RESOURCES = 2002; // 0x7d2
+ field public static final int MIP_FA_MALFORMED_REPLY = 2007; // 0x7d7
+ field public static final int MIP_FA_MALFORMED_REQUEST = 2006; // 0x7d6
+ field public static final int MIP_FA_MISSING_CHALLENGE = 2017; // 0x7e1
+ field public static final int MIP_FA_MISSING_HOME_ADDRESS = 2015; // 0x7df
+ field public static final int MIP_FA_MISSING_HOME_AGENT = 2014; // 0x7de
+ field public static final int MIP_FA_MISSING_NAI = 2013; // 0x7dd
+ field public static final int MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE = 2003; // 0x7d3
+ field public static final int MIP_FA_REASON_UNSPECIFIED = 2000; // 0x7d0
+ field public static final int MIP_FA_REQUESTED_LIFETIME_TOO_LONG = 2005; // 0x7d5
+ field public static final int MIP_FA_REVERSE_TUNNEL_IS_MANDATORY = 2011; // 0x7db
+ field public static final int MIP_FA_REVERSE_TUNNEL_UNAVAILABLE = 2010; // 0x7da
+ field public static final int MIP_FA_STALE_CHALLENGE = 2018; // 0x7e2
+ field public static final int MIP_FA_UNKNOWN_CHALLENGE = 2016; // 0x7e0
+ field public static final int MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE = 2009; // 0x7d9
+ field public static final int MIP_HA_ADMIN_PROHIBITED = 2020; // 0x7e4
+ field public static final int MIP_HA_ENCAPSULATION_UNAVAILABLE = 2029; // 0x7ed
+ field public static final int MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE = 2023; // 0x7e7
+ field public static final int MIP_HA_INSUFFICIENT_RESOURCES = 2021; // 0x7e5
+ field public static final int MIP_HA_MALFORMED_REQUEST = 2025; // 0x7e9
+ field public static final int MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE = 2022; // 0x7e6
+ field public static final int MIP_HA_REASON_UNSPECIFIED = 2019; // 0x7e3
+ field public static final int MIP_HA_REGISTRATION_ID_MISMATCH = 2024; // 0x7e8
+ field public static final int MIP_HA_REVERSE_TUNNEL_IS_MANDATORY = 2028; // 0x7ec
+ field public static final int MIP_HA_REVERSE_TUNNEL_UNAVAILABLE = 2027; // 0x7eb
+ field public static final int MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS = 2026; // 0x7ea
field public static final int MISSING_UNKNOWN_APN = 27; // 0x1b
+ field public static final int MODEM_APP_PREEMPTED = 2032; // 0x7f0
+ field public static final int MODEM_RESTART = 2037; // 0x7f5
+ field public static final int MSC_TEMPORARILY_NOT_REACHABLE = 2180; // 0x884
field public static final int MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE = 101; // 0x65
field public static final int MSG_TYPE_NONCOMPATIBLE_STATE = 98; // 0x62
+ field public static final int MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK = 2099; // 0x833
+ field public static final int MULTIPLE_PDP_CALL_NOT_ALLOWED = 2192; // 0x890
field public static final int MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED = 55; // 0x37
+ field public static final int NAS_LAYER_FAILURE = 2191; // 0x88f
+ field public static final int NAS_REQUEST_REJECTED_BY_NETWORK = 2167; // 0x877
field public static final int NAS_SIGNALLING = 14; // 0xe
field public static final int NETWORK_FAILURE = 38; // 0x26
+ field public static final int NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH = 2154; // 0x86a
+ field public static final int NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH = 2153; // 0x869
+ field public static final int NETWORK_INITIATED_TERMINATION = 2031; // 0x7ef
field public static final int NONE = 0; // 0x0
+ field public static final int NON_IP_NOT_SUPPORTED = 2069; // 0x815
+ field public static final int NORMAL_RELEASE = 2218; // 0x8aa
+ field public static final int NO_CDMA_SERVICE = 2084; // 0x824
+ field public static final int NO_COLLOCATED_HDR = 2225; // 0x8b1
+ field public static final int NO_EPS_BEARER_CONTEXT_ACTIVATED = 2189; // 0x88d
+ field public static final int NO_GPRS_CONTEXT = 2094; // 0x82e
+ field public static final int NO_HYBRID_HDR_SERVICE = 2209; // 0x8a1
+ field public static final int NO_PDP_CONTEXT_ACTIVATED = 2107; // 0x83b
+ field public static final int NO_RESPONSE_FROM_BASE_STATION = 2081; // 0x821
+ field public static final int NO_SERVICE = 2216; // 0x8a8
+ field public static final int NO_SERVICE_ON_GATEWAY = 2093; // 0x82d
field public static final int NSAPI_IN_USE = 35; // 0x23
+ field public static final int NULL_APN_DISALLOWED = 2061; // 0x80d
field public static final int OEM_DCFAILCAUSE_1 = 4097; // 0x1001
field public static final int OEM_DCFAILCAUSE_10 = 4106; // 0x100a
field public static final int OEM_DCFAILCAUSE_11 = 4107; // 0x100b
@@ -6765,33 +7100,126 @@
field public static final int OEM_DCFAILCAUSE_7 = 4103; // 0x1007
field public static final int OEM_DCFAILCAUSE_8 = 4104; // 0x1008
field public static final int OEM_DCFAILCAUSE_9 = 4105; // 0x1009
+ field public static final int ONLY_IPV4V6_ALLOWED = 57; // 0x39
field public static final int ONLY_IPV4_ALLOWED = 50; // 0x32
field public static final int ONLY_IPV6_ALLOWED = 51; // 0x33
+ field public static final int ONLY_NON_IP_ALLOWED = 58; // 0x3a
field public static final int ONLY_SINGLE_BEARER_ALLOWED = 52; // 0x34
field public static final int OPERATOR_BARRED = 8; // 0x8
+ field public static final int OTASP_COMMIT_IN_PROGRESS = 2208; // 0x8a0
field public static final int PDN_CONN_DOES_NOT_EXIST = 54; // 0x36
+ field public static final int PDN_INACTIVITY_TIMER_EXPIRED = 2051; // 0x803
+ field public static final int PDN_IPV4_CALL_DISALLOWED = 2033; // 0x7f1
+ field public static final int PDN_IPV4_CALL_THROTTLED = 2034; // 0x7f2
+ field public static final int PDN_IPV6_CALL_DISALLOWED = 2035; // 0x7f3
+ field public static final int PDN_IPV6_CALL_THROTTLED = 2036; // 0x7f4
+ field public static final int PDN_NON_IP_CALL_DISALLOWED = 2071; // 0x817
+ field public static final int PDN_NON_IP_CALL_THROTTLED = 2070; // 0x816
+ field public static final int PDP_ACTIVATE_MAX_RETRY_FAILED = 2109; // 0x83d
+ field public static final int PDP_DUPLICATE = 2104; // 0x838
+ field public static final int PDP_ESTABLISH_TIMEOUT_EXPIRED = 2161; // 0x871
+ field public static final int PDP_INACTIVE_TIMEOUT_EXPIRED = 2163; // 0x873
+ field public static final int PDP_LOWERLAYER_ERROR = 2164; // 0x874
+ field public static final int PDP_MODIFY_COLLISION = 2165; // 0x875
+ field public static final int PDP_MODIFY_TIMEOUT_EXPIRED = 2162; // 0x872
+ field public static final int PDP_PPP_NOT_SUPPORTED = 2038; // 0x7f6
field public static final int PDP_WITHOUT_ACTIVE_TFT = 46; // 0x2e
+ field public static final int PHONE_IN_USE = 2222; // 0x8ae
+ field public static final int PHYSICAL_LINK_CLOSE_IN_PROGRESS = 2040; // 0x7f8
+ field public static final int PLMN_NOT_ALLOWED = 2101; // 0x835
+ field public static final int PPP_AUTH_FAILURE = 2229; // 0x8b5
+ field public static final int PPP_CHAP_FAILURE = 2232; // 0x8b8
+ field public static final int PPP_CLOSE_IN_PROGRESS = 2233; // 0x8b9
+ field public static final int PPP_OPTION_MISMATCH = 2230; // 0x8b6
+ field public static final int PPP_PAP_FAILURE = 2231; // 0x8b7
+ field public static final int PPP_TIMEOUT = 2228; // 0x8b4
field public static final int PREF_RADIO_TECH_CHANGED = -4; // 0xfffffffc
+ field public static final int PROFILE_BEARER_INCOMPATIBLE = 2042; // 0x7fa
field public static final int PROTOCOL_ERRORS = 111; // 0x6f
field public static final int QOS_NOT_ACCEPTED = 37; // 0x25
+ field public static final int RADIO_ACCESS_BEARER_FAILURE = 2110; // 0x83e
+ field public static final int RADIO_ACCESS_BEARER_SETUP_FAILURE = 2160; // 0x870
field public static final int RADIO_NOT_AVAILABLE = 65537; // 0x10001
field public static final int RADIO_POWER_OFF = -5; // 0xfffffffb
+ field public static final int REDIRECTION_OR_HANDOFF_IN_PROGRESS = 2220; // 0x8ac
field public static final int REGISTRATION_FAIL = -1; // 0xffffffff
field public static final int REGULAR_DEACTIVATION = 36; // 0x24
+ field public static final int REJECTED_BY_BASE_STATION = 2082; // 0x822
+ field public static final int RRC_CONNECTION_ABORTED_AFTER_HANDOVER = 2173; // 0x87d
+ field public static final int RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE = 2174; // 0x87e
+ field public static final int RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE = 2171; // 0x87b
+ field public static final int RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE = 2175; // 0x87f
+ field public static final int RRC_CONNECTION_ABORT_REQUEST = 2151; // 0x867
+ field public static final int RRC_CONNECTION_ACCESS_BARRED = 2139; // 0x85b
+ field public static final int RRC_CONNECTION_ACCESS_STRATUM_FAILURE = 2137; // 0x859
+ field public static final int RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS = 2138; // 0x85a
+ field public static final int RRC_CONNECTION_CELL_NOT_CAMPED = 2144; // 0x860
+ field public static final int RRC_CONNECTION_CELL_RESELECTION = 2140; // 0x85c
+ field public static final int RRC_CONNECTION_CONFIG_FAILURE = 2141; // 0x85d
+ field public static final int RRC_CONNECTION_INVALID_REQUEST = 2168; // 0x878
+ field public static final int RRC_CONNECTION_LINK_FAILURE = 2143; // 0x85f
+ field public static final int RRC_CONNECTION_NORMAL_RELEASE = 2147; // 0x863
+ field public static final int RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER = 2150; // 0x866
+ field public static final int RRC_CONNECTION_RADIO_LINK_FAILURE = 2148; // 0x864
+ field public static final int RRC_CONNECTION_REESTABLISHMENT_FAILURE = 2149; // 0x865
+ field public static final int RRC_CONNECTION_REJECT_BY_NETWORK = 2146; // 0x862
+ field public static final int RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE = 2172; // 0x87c
+ field public static final int RRC_CONNECTION_RF_UNAVAILABLE = 2170; // 0x87a
+ field public static final int RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR = 2152; // 0x868
+ field public static final int RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE = 2145; // 0x861
+ field public static final int RRC_CONNECTION_TIMER_EXPIRED = 2142; // 0x85e
+ field public static final int RRC_CONNECTION_TRACKING_AREA_ID_CHANGED = 2169; // 0x879
+ field public static final int RRC_UPLINK_CONNECTION_RELEASE = 2134; // 0x856
+ field public static final int RRC_UPLINK_DATA_TRANSMISSION_FAILURE = 2132; // 0x854
+ field public static final int RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER = 2133; // 0x855
+ field public static final int RRC_UPLINK_ERROR_REQUEST_FROM_NAS = 2136; // 0x858
+ field public static final int RRC_UPLINK_RADIO_LINK_FAILURE = 2135; // 0x857
+ field public static final int RUIM_NOT_PRESENT = 2085; // 0x825
+ field public static final int SECURITY_MODE_REJECTED = 2186; // 0x88a
+ field public static final int SERVICE_NOT_ALLOWED_ON_PLMN = 2129; // 0x851
field public static final int SERVICE_OPTION_NOT_SUBSCRIBED = 33; // 0x21
field public static final int SERVICE_OPTION_NOT_SUPPORTED = 32; // 0x20
field public static final int SERVICE_OPTION_OUT_OF_ORDER = 34; // 0x22
field public static final int SIGNAL_LOST = -3; // 0xfffffffd
+ field public static final int SIM_CARD_CHANGED = 2043; // 0x7fb
+ field public static final int SYNCHRONIZATION_FAILURE = 2184; // 0x888
+ field public static final int TEST_LOOPBACK_REGULAR_DEACTIVATION = 2196; // 0x894
field public static final int TETHERED_CALL_ACTIVE = -6; // 0xfffffffa
field public static final int TFT_SEMANTIC_ERROR = 41; // 0x29
field public static final int TFT_SYTAX_ERROR = 42; // 0x2a
+ field public static final int THERMAL_EMERGENCY = 2090; // 0x82a
+ field public static final int THERMAL_MITIGATION = 2062; // 0x80e
+ field public static final int TRAT_SWAP_FAILED = 2048; // 0x800
+ field public static final int UE_INITIATED_DETACH_OR_DISCONNECT = 128; // 0x80
+ field public static final int UE_IS_ENTERING_POWERSAVE_MODE = 2226; // 0x8b2
+ field public static final int UE_RAT_CHANGE = 2105; // 0x839
+ field public static final int UE_SECURITY_CAPABILITIES_MISMATCH = 2185; // 0x889
+ field public static final int UMTS_HANDOVER_TO_IWLAN = 2199; // 0x897
field public static final int UMTS_REACTIVATION_REQ = 39; // 0x27
+ field public static final int UNACCEPTABLE_NON_EPS_AUTHENTICATION = 2187; // 0x88b
field public static final int UNKNOWN = 65536; // 0x10000
field public static final int UNKNOWN_INFO_ELEMENT = 99; // 0x63
field public static final int UNKNOWN_PDP_ADDRESS_TYPE = 28; // 0x1c
field public static final int UNKNOWN_PDP_CONTEXT = 43; // 0x2b
+ field public static final int UNPREFERRED_RAT = 2039; // 0x7f7
+ field public static final int UNSUPPORTED_1X_PREV = 2214; // 0x8a6
field public static final int UNSUPPORTED_APN_IN_CURRENT_PLMN = 66; // 0x42
+ field public static final int UNSUPPORTED_QCI_VALUE = 59; // 0x3b
field public static final int USER_AUTHENTICATION = 29; // 0x1d
+ field public static final int VSNCP_ADMINISTRATIVELY_PROHIBITED = 2245; // 0x8c5
+ field public static final int VSNCP_APN_UNATHORIZED = 2238; // 0x8be
+ field public static final int VSNCP_GEN_ERROR = 2237; // 0x8bd
+ field public static final int VSNCP_INSUFFICIENT_PARAMETERS = 2243; // 0x8c3
+ field public static final int VSNCP_NO_PDN_GATEWAY_ADDRESS = 2240; // 0x8c0
+ field public static final int VSNCP_PDN_EXISTS_FOR_THIS_APN = 2248; // 0x8c8
+ field public static final int VSNCP_PDN_GATEWAY_REJECT = 2242; // 0x8c2
+ field public static final int VSNCP_PDN_GATEWAY_UNREACHABLE = 2241; // 0x8c1
+ field public static final int VSNCP_PDN_ID_IN_USE = 2246; // 0x8c6
+ field public static final int VSNCP_PDN_LIMIT_EXCEEDED = 2239; // 0x8bf
+ field public static final int VSNCP_RECONNECT_NOT_ALLOWED = 2249; // 0x8c9
+ field public static final int VSNCP_RESOURCE_UNAVAILABLE = 2244; // 0x8c4
+ field public static final int VSNCP_SUBSCRIBER_LIMITATION = 2247; // 0x8c7
+ field public static final int VSNCP_TIMEOUT = 2236; // 0x8bc
}
public class DisconnectCause {
@@ -7156,10 +7584,13 @@
public class SubscriptionManager {
method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int);
method public void requestEmbeddedSubscriptionInfoListRefresh();
method public void requestEmbeddedSubscriptionInfoListRefresh(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultDataSubId(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultSmsSubId(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean);
field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff
field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2
@@ -7231,6 +7662,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState();
method public int getSimApplicationState();
method public int getSimCardState();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getSimLocale();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSupportedRadioAccessFamily();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.UiccCardInfo[] getUiccCardsInfo();
@@ -7533,9 +7965,12 @@
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata(android.telephony.euicc.DownloadableSubscription, android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus();
+ field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
field @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public static final String ACTION_OTA_STATUS_CHANGED = "android.telephony.euicc.action.OTA_STATUS_CHANGED";
field public static final String ACTION_PROFILE_SELECTION = "android.telephony.euicc.action.PROFILE_SELECTION";
field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+ field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
+ field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
field public static final int EUICC_ACTIVATION_TYPE_BACKUP = 2; // 0x2
field public static final int EUICC_ACTIVATION_TYPE_DEFAULT = 1; // 0x1
field public static final int EUICC_ACTIVATION_TYPE_TRANSFER = 3; // 0x3
@@ -7546,7 +7981,10 @@
field public static final int EUICC_OTA_SUCCEEDED = 3; // 0x3
field public static final String EXTRA_ACTIVATION_TYPE = "android.telephony.euicc.extra.ACTIVATION_TYPE";
field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS";
+ field public static final String EXTRA_ENABLE_SUBSCRIPTION = "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION";
field public static final String EXTRA_FORCE_PROVISION = "android.telephony.euicc.extra.FORCE_PROVISION";
+ field public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.euicc.extra.SUBSCRIPTION_ID";
+ field public static final String EXTRA_SUBSCRIPTION_NICKNAME = "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME";
}
@IntDef(prefix={"EUICC_OTA_"}, value={android.telephony.euicc.EuiccManager.EUICC_OTA_IN_PROGRESS, android.telephony.euicc.EuiccManager.EUICC_OTA_FAILED, android.telephony.euicc.EuiccManager.EUICC_OTA_SUCCEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_NOT_NEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_STATUS_UNAVAILABLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccManager.OtaStatus {
@@ -8601,6 +9039,22 @@
package android.util {
+ public class DocumentsStatsLog {
+ method public static void logActivityLaunch(int, boolean, int, int);
+ method public static void logFileOperation(int, int);
+ method public static void logFileOperationCanceled(int);
+ method public static void logFileOperationCopyMoveMode(int, int);
+ method public static void logFileOperationFailure(int, int);
+ method public static void logFilePick(int, long, int, boolean, int, int, int);
+ method public static void logInvalidScopedAccessRequest(int);
+ method public static void logPickerLaunchedFrom(String);
+ method public static void logRootVisited(int, int);
+ method public static void logSearchMode(int);
+ method public static void logSearchType(int);
+ method public static void logStartupMs(int);
+ method public static void logUserAction(int);
+ }
+
public class EventLog {
method public static void readEventsOnWrapping(int[], long, java.util.Collection<android.util.EventLog.Event>) throws java.io.IOException;
}
@@ -8641,9 +9095,22 @@
}
+package android.view.autofill {
+
+ public final class AutofillManager {
+ method @NonNull public java.util.Set<android.content.ComponentName> getAugmentedAutofillDisabledActivities();
+ method @NonNull public java.util.Set<java.lang.String> getAugmentedAutofillDisabledPackages();
+ method public void setActivityAugmentedAutofillEnabled(@NonNull android.content.ComponentName, boolean);
+ method public void setAugmentedAutofillWhitelist(@Nullable java.util.List<java.lang.String>, @Nullable java.util.List<android.content.ComponentName>);
+ method public void setPackageAugmentedAutofillEnabled(@NonNull String, boolean);
+ }
+
+}
+
package android.view.contentcapture {
public final class ContentCaptureContext implements android.os.Parcelable {
+ method @Nullable public String getAction();
method @Nullable public android.content.ComponentName getActivityComponent();
method public int getDisplayId();
method @Nullable public android.os.Bundle getExtras();
@@ -9096,6 +9563,10 @@
field public final android.content.pm.Signature[] signatures;
}
+ public abstract class WebViewRenderer {
+ ctor public WebViewRenderer();
+ }
+
public final class WebViewUpdateService {
method public static android.webkit.WebViewProviderInfo[] getAllWebViewPackages();
method public static String getCurrentWebViewPackageName();
diff --git a/api/test-current.txt b/api/test-current.txt
index 0d88937..223ddb9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -352,6 +352,10 @@
method public boolean isReservedSupported(@NonNull java.util.UUID);
}
+ public final class UsageStatsManager {
+ method public void forceUsageSourceSettingRead();
+ }
+
}
package android.bluetooth {
@@ -772,6 +776,11 @@
field public static final java.util.UUID EFFECT_TYPE_NULL;
}
+ public static class AudioEffect.Descriptor {
+ ctor public AudioEffect.Descriptor(android.os.Parcel);
+ method public void writeToParcel(android.os.Parcel);
+ }
+
public static interface AudioEffect.OnParameterChangeListener {
method public void onParameterChange(android.media.audiofx.AudioEffect, int, byte[], byte[]);
}
@@ -781,6 +790,8 @@
package android.net {
public class CaptivePortal implements android.os.Parcelable {
+ ctor public CaptivePortal(android.os.IBinder);
+ method public void useNetwork();
field public static final int APP_RETURN_DISMISSED = 0; // 0x0
field public static final int APP_RETURN_UNWANTED = 1; // 0x1
field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
@@ -791,17 +802,46 @@
field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
}
+ public final class IpPrefix implements android.os.Parcelable {
+ ctor public IpPrefix(java.net.InetAddress, int);
+ }
+
public final class IpSecManager {
field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
public class LinkAddress implements android.os.Parcelable {
+ ctor public LinkAddress(java.net.InetAddress, int, int, int);
method public boolean isGlobalPreferred();
method public boolean isIPv4();
method public boolean isIPv6();
method public boolean isSameAddressAs(android.net.LinkAddress);
}
+ public final class LinkProperties implements android.os.Parcelable {
+ ctor public LinkProperties(android.net.LinkProperties);
+ method public boolean addDnsServer(java.net.InetAddress);
+ method @Nullable public android.net.IpPrefix getNat64Prefix();
+ method public java.util.List<java.net.InetAddress> getPcscfServers();
+ method public String getTcpBufferSizes();
+ method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
+ method public boolean hasGlobalIPv6Address();
+ method public boolean hasIPv4Address();
+ method public boolean hasIPv6DefaultRoute();
+ method public boolean isIPv4Provisioned();
+ method public boolean isIPv6Provisioned();
+ method public boolean isProvisioned();
+ method public boolean isReachable(java.net.InetAddress);
+ method public boolean removeDnsServer(java.net.InetAddress);
+ method public boolean removeRoute(android.net.RouteInfo);
+ method public void setNat64Prefix(android.net.IpPrefix);
+ method public void setPcscfServers(java.util.Collection<java.net.InetAddress>);
+ method public void setPrivateDnsServerName(@Nullable String);
+ method public void setTcpBufferSizes(String);
+ method public void setUsePrivateDns(boolean);
+ method public void setValidatedPrivateDnsServers(java.util.Collection<java.net.InetAddress>);
+ }
+
public class Network implements android.os.Parcelable {
method public android.net.Network getPrivateDnsBypassingCopy();
}
@@ -809,9 +849,11 @@
public final class NetworkCapabilities implements android.os.Parcelable {
method public int[] getCapabilities();
method public int[] getTransportTypes();
+ method public boolean satisfiedByNetworkCapabilities(android.net.NetworkCapabilities);
}
public final class RouteInfo implements android.os.Parcelable {
+ ctor public RouteInfo(android.net.IpPrefix, java.net.InetAddress, String, int);
method public int getType();
field public static final int RTN_THROW = 9; // 0x9
field public static final int RTN_UNICAST = 1; // 0x1
@@ -898,8 +940,10 @@
}
public class IpConnectivityLog {
+ ctor public IpConnectivityLog();
method public boolean log(long, android.net.metrics.IpConnectivityLog.Event);
method public boolean log(String, android.net.metrics.IpConnectivityLog.Event);
+ method public boolean log(android.net.Network, int[], android.net.metrics.IpConnectivityLog.Event);
method public boolean log(int, int[], android.net.metrics.IpConnectivityLog.Event);
method public boolean log(android.net.metrics.IpConnectivityLog.Event);
}
@@ -945,6 +989,20 @@
field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3
}
+ public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event {
+ }
+
+ public static class RaEvent.Builder {
+ ctor public RaEvent.Builder();
+ method public android.net.metrics.RaEvent build();
+ method public android.net.metrics.RaEvent.Builder updateDnsslLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updatePrefixPreferredLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updatePrefixValidLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updateRdnssLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updateRouteInfoLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updateRouterLifetime(long);
+ }
+
public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
method public static String getProbeName(int);
field public static final int DNS_FAILURE = 0; // 0x0
@@ -1241,15 +1299,11 @@
method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context);
method public abstract long getDuration();
method protected static int scale(int, float, int);
- field public static final int EFFECT_CLICK = 0; // 0x0
- field public static final int EFFECT_DOUBLE_CLICK = 1; // 0x1
- field public static final int EFFECT_HEAVY_CLICK = 5; // 0x5
field public static final int EFFECT_POP = 4; // 0x4
field public static final int EFFECT_STRENGTH_LIGHT = 0; // 0x0
field public static final int EFFECT_STRENGTH_MEDIUM = 1; // 0x1
field public static final int EFFECT_STRENGTH_STRONG = 2; // 0x2
field public static final int EFFECT_THUD = 3; // 0x3
- field public static final int EFFECT_TICK = 2; // 0x2
field public static final int[] RINGTONES;
}
@@ -1656,10 +1710,7 @@
method public int getTaskId();
}
- public final class FillResponse implements android.os.Parcelable {
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.autofill.augmented.FillResponse> CREATOR;
+ public final class FillResponse {
}
public static final class FillResponse.Builder {
@@ -1673,7 +1724,6 @@
ctor public FillWindow();
method public void destroy();
method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long);
- field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L
}
public abstract class PresentationParams {
@@ -1697,84 +1747,14 @@
package android.service.notification {
- public final class Adjustment implements android.os.Parcelable {
- ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int);
- ctor protected Adjustment(android.os.Parcel);
- method public int describeContents();
- method public CharSequence getExplanation();
- method public String getKey();
- method public String getPackage();
- method public android.os.Bundle getSignals();
- method public int getUser();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
- field public static final String KEY_IMPORTANCE = "key_importance";
- field public static final String KEY_PEOPLE = "key_people";
- field public static final String KEY_SMART_ACTIONS = "key_smart_actions";
- field public static final String KEY_SMART_REPLIES = "key_smart_replies";
- field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
- field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
- }
-
@Deprecated public abstract class ConditionProviderService extends android.app.Service {
method @Deprecated public boolean isBound();
}
- public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
- ctor public NotificationAssistantService();
- method public final void adjustNotification(android.service.notification.Adjustment);
- method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
- method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
- method public final android.os.IBinder onBind(android.content.Intent);
- method public void onNotificationDirectReplied(@NonNull String);
- method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
- method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
- method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
- method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, String);
- method public void onNotificationsSeen(java.util.List<java.lang.String>);
- method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
- method public final void unsnoozeNotification(String);
- field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
- field public static final int SOURCE_FROM_APP = 0; // 0x0
- field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1
- }
-
public abstract class NotificationListenerService extends android.app.Service {
method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
}
- public final class NotificationStats implements android.os.Parcelable {
- ctor public NotificationStats();
- ctor protected NotificationStats(android.os.Parcel);
- method public int describeContents();
- method public int getDismissalSentiment();
- method public int getDismissalSurface();
- method public boolean hasDirectReplied();
- method public boolean hasExpanded();
- method public boolean hasInteracted();
- method public boolean hasSeen();
- method public boolean hasSnoozed();
- method public boolean hasViewedSettings();
- method public void setDirectReplied();
- method public void setDismissalSentiment(int);
- method public void setDismissalSurface(int);
- method public void setExpanded();
- method public void setSeen();
- method public void setSnoozed();
- method public void setViewedSettings();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
- field public static final int DISMISSAL_AOD = 2; // 0x2
- field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
- field public static final int DISMISSAL_OTHER = 0; // 0x0
- field public static final int DISMISSAL_PEEK = 1; // 0x1
- field public static final int DISMISSAL_SHADE = 3; // 0x3
- field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
- field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
- field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
- field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
- }
-
public final class SnoozeCriterion implements android.os.Parcelable {
ctor public SnoozeCriterion(String, CharSequence, CharSequence);
ctor protected SnoozeCriterion(android.os.Parcel);
@@ -2245,6 +2225,10 @@
method public static int getLongPressTooltipHideTimeout();
}
+ public class ViewDebug {
+ method @Nullable public static AutoCloseable startRenderingCommandsCapture(android.view.View, java.util.concurrent.Executor, java.util.function.Function<android.graphics.Picture,java.lang.Boolean>);
+ }
+
public interface WindowManager extends android.view.ViewManager {
method public default void setShouldShowIme(int, boolean);
method public default void setShouldShowSystemDecors(int, boolean);
@@ -2305,6 +2289,12 @@
}
public final class AutofillManager {
+ method @NonNull public java.util.Set<android.content.ComponentName> getAugmentedAutofillDisabledActivities();
+ method @NonNull public java.util.Set<java.lang.String> getAugmentedAutofillDisabledPackages();
+ method public void setActivityAugmentedAutofillEnabled(@NonNull android.content.ComponentName, boolean);
+ method public void setAugmentedAutofillWhitelist(@Nullable java.util.List<java.lang.String>, @Nullable java.util.List<android.content.ComponentName>);
+ method public void setPackageAugmentedAutofillEnabled(@NonNull String, boolean);
+ field public static final int FLAG_SMART_SUGGESTION_SYSTEM = 1; // 0x1
field public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 120000; // 0x1d4c0
}
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 59b2aa6..ca10482 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -74,7 +74,6 @@
"src/external/SubsystemSleepStatePuller.cpp",
"src/external/PowerStatsPuller.cpp",
"src/external/ResourceHealthManagerPuller.cpp",
- "src/external/ResourceThermalManagerPuller.cpp",
"src/external/StatsPullerManager.cpp",
"src/external/puller_util.cpp",
"src/logd/LogEvent.cpp",
@@ -136,7 +135,6 @@
"android.hardware.power@1.0",
"android.hardware.power@1.1",
"android.hardware.power.stats@1.0",
- "android.hardware.thermal@2.0",
"libpackagelistparser",
"libsysutils",
"libcutils",
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 820da55..9c320d3 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -1097,6 +1097,30 @@
return hardware::Void();
}
+hardware::Return<void> StatsService::reportSpeechDspStat(
+ const SpeechDspStat& speechDspStat) {
+ LogEvent event(getWallClockSec() * NS_PER_SEC, getElapsedRealtimeNs(), speechDspStat);
+ mProcessor->OnLogEvent(&event);
+
+ return hardware::Void();
+}
+
+hardware::Return<void> StatsService::reportVendorAtom(const VendorAtom& vendorAtom) {
+ std::string reverseDomainName = (std::string) vendorAtom.reverseDomainName;
+ if (vendorAtom.atomId < 100000 || vendorAtom.atomId >= 200000) {
+ ALOGE("Atom ID %ld is not a valid vendor atom ID", (long) vendorAtom.atomId);
+ return hardware::Void();
+ }
+ if (reverseDomainName.length() > 50) {
+ ALOGE("Vendor atom reverse domain name %s is too long.", reverseDomainName.c_str());
+ return hardware::Void();
+ }
+ LogEvent event(getWallClockSec() * NS_PER_SEC, getElapsedRealtimeNs(), vendorAtom);
+ mProcessor->OnLogEvent(&event);
+
+ return hardware::Void();
+}
+
void StatsService::binderDied(const wp <IBinder>& who) {
ALOGW("statscompanion service died");
StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec());
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index e9b3d4f..fe0504f 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -205,6 +205,17 @@
virtual Return<void> reportUsbPortOverheatEvent(
const UsbPortOverheatEvent& usbPortOverheatEvent) override;
+ /**
+ * Binder call to get Speech DSP state atom.
+ */
+ virtual Return<void> reportSpeechDspStat(
+ const SpeechDspStat& speechDspStat) override;
+
+ /**
+ * Binder call to get vendor atom.
+ */
+ virtual Return<void> reportVendorAtom(const VendorAtom& vendorAtom) override;
+
/** IBinder::DeathRecipient */
virtual void binderDied(const wp<IBinder>& who) override;
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index f8825ac..8fb01b4 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -27,12 +27,14 @@
import "frameworks/base/core/proto/android/bluetooth/enums.proto";
import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto";
import "frameworks/base/core/proto/android/bluetooth/hfp/enums.proto";
+import "frameworks/base/core/proto/android/debug/enums.proto";
import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
import "frameworks/base/core/proto/android/os/enums.proto";
import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto";
import "frameworks/base/core/proto/android/server/enums.proto";
import "frameworks/base/core/proto/android/server/location/enums.proto";
import "frameworks/base/core/proto/android/service/procstats_enum.proto";
+import "frameworks/base/core/proto/android/service/usb.proto";
import "frameworks/base/core/proto/android/stats/enums.proto";
import "frameworks/base/core/proto/android/stats/docsui/docsui_enums.proto";
import "frameworks/base/core/proto/android/stats/launcher/launcher.proto";
@@ -194,6 +196,19 @@
GnssNfwNotificationReported gnss_nfw_notification_reported = 131;
GnssConfigurationReported gnss_configuration_reported = 132;
UsbPortOverheatEvent usb_port_overheat_event_reported = 133;
+ NfcErrorOccurred nfc_error_occurred = 134;
+ NfcStateChanged nfc_state_changed = 135;
+ NfcBeamOccurred nfc_beam_occurred = 136;
+ NfcCardemulationOccurred nfc_cardemulation_occurred = 137;
+ NfcTagOccurred nfc_tag_occurred = 138;
+ NfcHceTransactionOccurred nfc_hce_transaction_occurred = 139;
+ SeStateChanged se_state_changed = 140;
+ SeOmapiReported se_omapi_reported = 141;
+ BroadcastDispatchLatencyReported broadcast_dispatch_latency_reported = 142;
+ AttentionManagerServiceResultReported attention_manager_service_result_reported = 143;
+ AdbConnectionChanged adb_connection_changed = 144;
+ SpeechDspStatReported speech_dsp_stat_reported = 145;
+ UsbContaminantReported usb_contaminant_reported = 146;
}
// Pulled events will start at field 10000.
@@ -244,6 +259,7 @@
ProcessMemoryHighWaterMark process_memory_high_water_mark = 10042;
BatteryLevel battery_level = 10043;
BuildInformation build_information = 10044;
+ BatteryCycleCount battery_cycle_count = 10045;
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -2514,6 +2530,19 @@
optional int32 time_to_inactive_secs = 5;
};
+/**
+ * Logs total effective full charge and discharge cycles on a battery.
+ * Here are some examples of one effective cycle:
+ * 1) the battery charges from 0% to 100% and drains back to 0%,
+ * 2) charging from 50% to 100% and draining back to 50% twice.
+ * Pulled from:
+ * frameworks/base/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
+ */
+message BatteryCycleCount {
+ /* Number of total charge and discharge cycles on the system battery. */
+ optional int32 cycle_count = 1;
+}
+
/*
* Logs when a connection becomes available and lost.
* Logged in StatsCompanionService.java
@@ -2992,8 +3021,8 @@
/**
* Pulls battery coulomb counter, which is the remaining battery charge in uAh.
- * Pulled from:
- * frameworks/base/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
+ *
+ * Pulled from StatsCompanionService.java
*/
message RemainingBatteryCapacity {
optional int32 charge_micro_ampere_hour = 1;
@@ -4309,3 +4338,214 @@
// with spaces as separators.
optional string enabled_proxy_app_package_name_list = 13;
}
+
+/**
+ * Logs when a NFC device's error occurred.
+ * Logged from:
+ * system/nfc/src/nfc/nfc/nfc_ncif.cc
+ * packages/apps/Nfc/src/com/android/nfc/cardemulation/AidRoutingManager.java
+ */
+message NfcErrorOccurred {
+ enum Type {
+ UNKNOWN = 0;
+ CMD_TIMEOUT = 1;
+ ERROR_NOTIFICATION = 2;
+ AID_OVERFLOW = 3;
+ }
+ optional Type type = 1;
+ // If it's nci cmd timeout, log the timeout command.
+ optional uint32 nci_cmd = 2;
+
+ optional uint32 error_ntf_status_code = 3;
+}
+
+/**
+ * Logs when a NFC device's state changed event
+ * Logged from:
+ * packages/apps/Nfc/src/com/android/nfc/NfcService.java
+ */
+message NfcStateChanged {
+ enum State {
+ UNKNOWN = 0;
+ OFF = 1;
+ ON = 2;
+ ON_LOCKED = 3; // Secure Nfc enabled.
+ CRASH_RESTART = 4; // NfcService watchdog timeout restart.
+ }
+ optional State state = 1;
+}
+
+/**
+ * Logs when a NFC Beam Transaction occurred.
+ * Logged from:
+ * packages/apps/Nfc/src/com/android/nfc/P2pLinkManager.java
+ */
+message NfcBeamOccurred {
+ enum Operation {
+ UNKNOWN = 0;
+ SEND = 1;
+ RECEIVE = 2;
+ }
+ optional Operation operation = 1;
+}
+
+/**
+ * Logs when a NFC Card Emulation Transaction occurred.
+ * Logged from:
+ * packages/apps/Nfc/src/com/android/nfc/cardemulation/HostEmulationManager.java
+ * packages/apps/Nfc/src/com/android/nfc/cardemulation/HostNfcFEmulationManager.java
+ */
+message NfcCardemulationOccurred {
+ enum Category {
+ UNKNOWN = 0;
+ HCE_PAYMENT = 1;
+ HCE_OTHER = 2;
+ OFFHOST = 3;
+ }
+ // Transaction belongs to HCE payment or HCE other category, or offhost.
+ optional Category category = 1;
+ // SeName from transaction: SIMx, eSEx, HCE, HCEF.
+ optional string se_name = 2;
+}
+
+/**
+ * Logs when a NFC Tag event occurred.
+ * Logged from:
+ * packages/apps/Nfc/src/com/android/nfc/NfcDispatcher.java
+ */
+message NfcTagOccurred {
+ enum Type {
+ UNKNOWN = 0;
+ URL = 1;
+ BT_PAIRING = 2;
+ PROVISION = 3;
+ WIFI_CONNECT = 4;
+ APP_LAUNCH = 5;
+ OTHERS = 6;
+ }
+ optional Type type = 1;
+}
+
+/**
+ * Logs when Hce transaction triggered
+ * Logged from:
+ * system/nfc/src/nfc/nfc/nfc_ncif.cc
+ */
+message NfcHceTransactionOccurred {
+ // The latency period(in microseconds) it took for the first HCE data
+ // exchange.
+ optional uint32 latency_micros = 1;
+}
+
+/**
+ * Logs when SecureElement state event changed
+ * Logged from:
+ * packages/apps/SecureElement/src/com/android/se/Terminal.java
+ */
+message SeStateChanged {
+ enum State {
+ UNKNOWN = 0;
+ INITIALIZED = 1;
+ DISCONNECTED = 2;
+ CONNECTED = 3;
+ HALCRASH = 4;
+ }
+ optional State state = 1;
+
+ optional string state_change_reason = 2;
+ // SIMx or eSEx.
+ optional string terminal = 3;
+}
+
+/**
+ * Logs when Omapi API used
+ * Logged from:
+ * packages/apps/SecureElement/src/com/android/se/Terminal.java
+ */
+message SeOmapiReported {
+ enum Operation {
+ UNKNOWN = 0;
+ OPEN_CHANNEL = 1;
+ }
+ optional Operation operation = 1;
+ // SIMx or eSEx.
+ optional string terminal = 2;
+
+ optional string package_name = 3;
+}
+
+/**
+ * Logs the dispatch latency of a broadcast during processing of BOOT_COMPLETED.
+ * The dispatch latency is the dispatchClockTime - enqueueClockTime.
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
+ */
+message BroadcastDispatchLatencyReported {
+ optional int64 dispatch_latency_millis = 1;
+}
+
+/**
+ * Logs AttentionManagerService attention check result.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/attention/AttentionManagerService.java
+ */
+message AttentionManagerServiceResultReported {
+ // See core/java/android/service/attention/AttentionService.java
+ enum AttentionCheckResult {
+ UNKNOWN = 20;
+ ATTENTION_SUCCESS_ABSENT = 0;
+ ATTENTION_SUCCESS_PRESENT = 1;
+ ATTENTION_FAILURE_PREEMPTED = 2;
+ ATTENTION_FAILURE_TIMED_OUT = 3;
+ ATTENTION_FAILURE_UNKNOWN = 4;
+ }
+ optional AttentionCheckResult attention_check_result = 1 [default = UNKNOWN];
+}
+
+/**
+ * Logs when an adb connection changes state.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+ */
+message AdbConnectionChanged {
+ // The last time this system connected via adb, or 0 if the 'always allow' option was not
+ // previously selected for this system.
+ optional int64 last_connection_time_millis = 1;
+
+ // The time in ms within which a subsequent connection from an 'always allow' system is allowed
+ // to reconnect via adb without user interaction.
+ optional int64 auth_window_millis = 2;
+
+ // The state of the adb connection from frameworks/base/core/proto/android/debug/enums.proto.
+ optional android.debug.AdbConnectionStateEnum state = 3;
+
+ // True if the 'always allow' option was selected for this system.
+ optional bool always_allow = 4;
+}
+
+/*
+ * Logs the reported speech DSP status.
+ *
+ * Logged from:
+ * Vendor audio implementation.
+ */
+message SpeechDspStatReported {
+ // The total Speech DSP uptime in milliseconds.
+ optional int32 total_uptime_millis = 1;
+ // The total Speech DSP downtime in milliseconds.
+ optional int32 total_downtime_millis = 2;
+ optional int32 total_crash_count = 3;
+ optional int32 total_recover_count = 4;
+}
+
+/**
+ * Logs USB connector contaminant status.
+ *
+ * Logged from: USB Service.
+ */
+message UsbContaminantReported {
+ optional string id = 1;
+ optional android.service.usb.ContaminantPresenceStatus status = 2;
+}
diff --git a/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp b/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
index b878652..75b63f4 100644
--- a/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
+++ b/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
@@ -24,16 +24,16 @@
#include "ResourceHealthManagerPuller.h"
#include "logd/LogEvent.h"
-#include "statslog.h"
#include "stats_log_util.h"
+#include "statslog.h"
using android::hardware::hidl_vec;
+using android::hardware::Return;
+using android::hardware::Void;
using android::hardware::health::V2_0::get_health_service;
using android::hardware::health::V2_0::HealthInfo;
using android::hardware::health::V2_0::IHealth;
using android::hardware::health::V2_0::Result;
-using android::hardware::Return;
-using android::hardware::Void;
using std::make_shared;
using std::shared_ptr;
@@ -75,35 +75,41 @@
}
if (mTagId == android::util::REMAINING_BATTERY_CAPACITY) {
auto ptr = make_shared<LogEvent>(android::util::REMAINING_BATTERY_CAPACITY,
- wallClockTimestampNs, elapsedTimestampNs);
+ wallClockTimestampNs, elapsedTimestampNs);
ptr->write(v.legacy.batteryChargeCounter);
ptr->init();
data->push_back(ptr);
} else if (mTagId == android::util::FULL_BATTERY_CAPACITY) {
auto ptr = make_shared<LogEvent>(android::util::FULL_BATTERY_CAPACITY,
- wallClockTimestampNs, elapsedTimestampNs);
+ wallClockTimestampNs, elapsedTimestampNs);
ptr->write(v.legacy.batteryFullCharge);
ptr->init();
data->push_back(ptr);
} else if (mTagId == android::util::BATTERY_VOLTAGE) {
- auto ptr = make_shared<LogEvent>(android::util::BATTERY_VOLTAGE,
- wallClockTimestampNs, elapsedTimestampNs);
+ auto ptr = make_shared<LogEvent>(android::util::BATTERY_VOLTAGE, wallClockTimestampNs,
+ elapsedTimestampNs);
ptr->write(v.legacy.batteryVoltage);
ptr->init();
data->push_back(ptr);
} else if (mTagId == android::util::BATTERY_LEVEL) {
- auto ptr = make_shared<LogEvent>(android::util::BATTERY_LEVEL,
- wallClockTimestampNs, elapsedTimestampNs);
- ptr->write(v.legacy.batteryLevel);
- ptr->init();
- data->push_back(ptr);
+ auto ptr = make_shared<LogEvent>(android::util::BATTERY_LEVEL, wallClockTimestampNs,
+ elapsedTimestampNs);
+ ptr->write(v.legacy.batteryLevel);
+ ptr->init();
+ data->push_back(ptr);
+ } else if (mTagId == android::util::BATTERY_CYCLE_COUNT) {
+ auto ptr = make_shared<LogEvent>(android::util::BATTERY_CYCLE_COUNT,
+ wallClockTimestampNs, elapsedTimestampNs);
+ ptr->write(v.legacy.batteryCycleCount);
+ ptr->init();
+ data->push_back(ptr);
} else {
ALOGE("Unsupported tag in ResourceHealthManagerPuller: %d", mTagId);
}
});
if (!result_success || !ret.isOk()) {
ALOGE("getHealthHal() failed: health HAL service not available. Description: %s",
- ret.description().c_str());
+ ret.description().c_str());
if (!ret.isOk() && ret.isDeadObject()) {
gHealthHal = nullptr;
}
diff --git a/cmds/statsd/src/external/ResourceThermalManagerPuller.cpp b/cmds/statsd/src/external/ResourceThermalManagerPuller.cpp
deleted file mode 100644
index 53709f1..0000000
--- a/cmds/statsd/src/external/ResourceThermalManagerPuller.cpp
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define DEBUG false // STOPSHIP if true
-#include "Log.h"
-
-#include <android/hardware/thermal/2.0/IThermal.h>
-#include "external/ResourceThermalManagerPuller.h"
-#include "external/StatsPuller.h"
-
-#include "ResourceThermalManagerPuller.h"
-#include "logd/LogEvent.h"
-#include "statslog.h"
-#include "stats_log_util.h"
-
-#include <chrono>
-
-using android::hardware::hidl_death_recipient;
-using android::hardware::hidl_vec;
-using android::hidl::base::V1_0::IBase;
-using ::android::hardware::thermal::V2_0::IThermal;
-using ::android::hardware::thermal::V2_0::Temperature;
-using ::android::hardware::thermal::V2_0::TemperatureType;
-using ::android::hardware::thermal::V1_0::ThermalStatus;
-using ::android::hardware::thermal::V1_0::ThermalStatusCode;
-using android::hardware::Return;
-using android::hardware::Void;
-
-using std::chrono::duration_cast;
-using std::chrono::nanoseconds;
-using std::chrono::system_clock;
-using std::make_shared;
-using std::shared_ptr;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-bool getThermalHalLocked();
-sp<android::hardware::thermal::V2_0::IThermal> gThermalHal = nullptr;
-std::mutex gThermalHalMutex;
-
-struct ThermalHalDeathRecipient : virtual public hidl_death_recipient {
- virtual void serviceDied(uint64_t cookie, const wp<IBase>& who) override {
- std::lock_guard<std::mutex> lock(gThermalHalMutex);
- ALOGE("ThermalHAL just died");
- gThermalHal = nullptr;
- getThermalHalLocked();
- }
-};
-
-sp<ThermalHalDeathRecipient> gThermalHalDeathRecipient = nullptr;
-
-// The caller must be holding gThermalHalMutex.
-bool getThermalHalLocked() {
- if (gThermalHal == nullptr) {
- gThermalHal = IThermal::getService();
- if (gThermalHal == nullptr) {
- ALOGE("Unable to get Thermal service.");
- } else {
- if (gThermalHalDeathRecipient == nullptr) {
- gThermalHalDeathRecipient = new ThermalHalDeathRecipient();
- }
- hardware::Return<bool> linked = gThermalHal->linkToDeath(
- gThermalHalDeathRecipient, 0x451F /* cookie */);
- if (!linked.isOk()) {
- ALOGE("Transaction error in linking to ThermalHAL death: %s",
- linked.description().c_str());
- gThermalHal = nullptr;
- } else if (!linked) {
- ALOGW("Unable to link to ThermalHal death notifications");
- gThermalHal = nullptr;
- } else {
- ALOGD("Link to death notification successful");
- }
- }
- }
- return gThermalHal != nullptr;
-}
-
-ResourceThermalManagerPuller::ResourceThermalManagerPuller() :
- StatsPuller(android::util::TEMPERATURE) {
-}
-
-bool ResourceThermalManagerPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
- std::lock_guard<std::mutex> lock(gThermalHalMutex);
- if (!getThermalHalLocked()) {
- ALOGE("Thermal Hal not loaded");
- return false;
- }
-
- int64_t wallClockTimestampNs = getWallClockNs();
- int64_t elapsedTimestampNs = getElapsedRealtimeNs();
-
- data->clear();
- bool resultSuccess = true;
-
- Return<void> ret = gThermalHal->getCurrentTemperatures(false, TemperatureType::SKIN,
- [&](ThermalStatus status, const hidl_vec<Temperature>& temps) {
- if (status.code != ThermalStatusCode::SUCCESS) {
- ALOGE("Failed to get temperatures from ThermalHAL. Status: %d", status.code);
- resultSuccess = false;
- return;
- }
- if (mTagId == android::util::TEMPERATURE) {
- for (size_t i = 0; i < temps.size(); ++i) {
- auto ptr = make_shared<LogEvent>(android::util::TEMPERATURE,
- wallClockTimestampNs, elapsedTimestampNs);
- ptr->write((static_cast<int>(temps[i].type)));
- ptr->write(temps[i].name);
- // Convert the temperature to an int.
- int32_t temp = static_cast<int>(temps[i].value * 10);
- ptr->write(temp);
- ptr->init();
- data->push_back(ptr);
- }
- } else {
- ALOGE("Unsupported tag in ResourceThermalManagerPuller: %d", mTagId);
- }
- });
- if (!ret.isOk()) {
- ALOGE("getThermalHalLocked() failed: thermal HAL service not available. Description: %s",
- ret.description().c_str());
- if (ret.isDeadObject()) {
- gThermalHal = nullptr;
- }
- return false;
- }
- return resultSuccess;
-}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/cmds/statsd/src/external/ResourceThermalManagerPuller.h b/cmds/statsd/src/external/ResourceThermalManagerPuller.h
deleted file mode 100644
index 5313792..0000000
--- a/cmds/statsd/src/external/ResourceThermalManagerPuller.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <utils/String16.h>
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Reads IThermal.hal
- */
-class ResourceThermalManagerPuller : public StatsPuller {
-public:
- ResourceThermalManagerPuller();
-
-private:
- bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
-};
-
-} // namespace statsd
-} // namespace os
-} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 4a716cf..7e56bee 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -28,7 +28,6 @@
#include "../statscompanion_util.h"
#include "PowerStatsPuller.h"
#include "ResourceHealthManagerPuller.h"
-#include "ResourceThermalManagerPuller.h"
#include "StatsCompanionServicePuller.h"
#include "StatsPullerManager.h"
#include "SubsystemSleepStatePuller.h"
@@ -131,9 +130,12 @@
// battery_voltage
{android::util::BATTERY_VOLTAGE,
{.puller = new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}},
- // battery_voltage
+ // battery_level
{android::util::BATTERY_LEVEL,
{.puller = new ResourceHealthManagerPuller(android::util::BATTERY_LEVEL)}},
+ // battery_cycle_count
+ {android::util::BATTERY_CYCLE_COUNT,
+ {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_CYCLE_COUNT)}},
// process_memory_state
{android::util::PROCESS_MEMORY_STATE,
{.additiveFields = {4, 5, 6, 7, 8, 9},
@@ -147,7 +149,8 @@
.puller =
new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}},
// temperature
- {android::util::TEMPERATURE, {.puller = new ResourceThermalManagerPuller()}},
+ {android::util::TEMPERATURE,
+ {.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}},
// binder_calls
{android::util::BINDER_CALLS,
{.additiveFields = {4, 5, 6, 8, 12},
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 78a75c5..eaba9be 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -267,6 +267,22 @@
}
LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+ const SpeechDspStat& speechDspStat) {
+ mLogdTimestampNs = wallClockTimestampNs;
+ mElapsedTimestampNs = elapsedTimestampNs;
+ mTagId = android::util::SPEECH_DSP_STAT_REPORTED;
+
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)),
+ Value(speechDspStat.totalUptimeMillis)));
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)),
+ Value(speechDspStat.totalDowntimeMillis)));
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)),
+ Value(speechDspStat.totalCrashCount)));
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)),
+ Value(speechDspStat.totalRecoverCount)));
+}
+
+LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
const BatteryCausedShutdown& batteryCausedShutdown) {
mLogdTimestampNs = wallClockTimestampNs;
mElapsedTimestampNs = elapsedTimestampNs;
@@ -294,6 +310,36 @@
Value(usbPortOverheatEvent.timeToInactive)));
}
+LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+ const VendorAtom& vendorAtom) {
+ mLogdTimestampNs = wallClockTimestampNs;
+ mElapsedTimestampNs = elapsedTimestampNs;
+ mTagId = vendorAtom.atomId;
+
+ mValues.push_back(
+ FieldValue(Field(mTagId, getSimpleField(1)), Value(vendorAtom.reverseDomainName)));
+ for (int i = 0; i < (int)vendorAtom.values.size(); i++) {
+ switch (vendorAtom.values[i].getDiscriminator()) {
+ case VendorAtom::Value::hidl_discriminator::intValue:
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+ Value(vendorAtom.values[i].intValue())));
+ break;
+ case VendorAtom::Value::hidl_discriminator::longValue:
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+ Value(vendorAtom.values[i].longValue())));
+ break;
+ case VendorAtom::Value::hidl_discriminator::floatValue:
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+ Value(vendorAtom.values[i].floatValue())));
+ break;
+ case VendorAtom::Value::hidl_discriminator::stringValue:
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+ Value(vendorAtom.values[i].stringValue())));
+ break;
+ }
+ }
+}
+
LogEvent::LogEvent(int32_t tagId, int64_t timestampNs) : LogEvent(tagId, timestampNs, 0) {}
LogEvent::LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid) {
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 3f47b7e..784376a 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -121,6 +121,12 @@
explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
const UsbPortOverheatEvent& usbPortOverheatEvent);
+ explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+ const SpeechDspStat& speechDspStat);
+
+ explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+ const VendorAtom& vendorAtom);
+
~LogEvent();
/**
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index e9c43cd..5cf012638 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -548,6 +548,7 @@
{"AID_LMKD", 1069},
{"AID_LLKD", 1070},
{"AID_IORAPD", 1071},
+ {"AID_NETWORK_STACK", 1073},
{"AID_SHELL", 2000},
{"AID_CACHE", 2001},
{"AID_DIAG", 2002}};
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index de818a8..d9aba61 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -55,7 +55,8 @@
"AID_ROOT",
"AID_BLUETOOTH",
"AID_LMKD",
- "com.android.managedprovisioning"
+ "com.android.managedprovisioning",
+ "AID_NETWORK_STACK"
};
private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName());
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index 4174ad7..1b7fbfe 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -23,8 +23,10 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.telecom.Log;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
+import android.text.TextUtils;
import com.android.internal.os.BaseCommand;
import com.android.internal.telecom.ITelecomService;
@@ -45,6 +47,8 @@
private static final String COMMAND_SET_PHONE_ACCOUNT_ENABLED = "set-phone-account-enabled";
private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled";
private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account";
+ private static final String COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT =
+ "set-user-selected-outgoing-phone-account";
private static final String COMMAND_REGISTER_SIM_PHONE_ACCOUNT = "register-sim-phone-account";
private static final String COMMAND_SET_TEST_CALL_REDIRECTION_APP = "set-test-call-redirection-app";
private static final String COMMAND_SET_TEST_CALL_SCREENING_APP = "set-test-call-screening-app";
@@ -70,6 +74,8 @@
+ "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n"
+ "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n"
+ "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n"
+ + "usage: telecom set-user-selected-outgoing-phone-account <COMPONENT> <ID> "
+ + "<USER_SN>\n"
+ "usage: telecom set-test-call-redirection-app <PACKAGE>\n"
+ "usage: telecom set-test-call-screening-app <PACKAGE>\n"
+ "usage: telecom set-test-auto-mode-app <PACKAGE>\n"
@@ -104,16 +110,18 @@
mTelecomService = ITelecomService.Stub.asInterface(
ServiceManager.getService(Context.TELECOM_SERVICE));
if (mTelecomService == null) {
+ Log.w(this, "onRun: Can't access telecom manager.");
showError("Error: Could not access the Telecom Manager. Is the system running?");
return;
}
mUserManager = IUserManager.Stub
.asInterface(ServiceManager.getService(Context.USER_SERVICE));
if (mUserManager == null) {
+ Log.w(this, "onRun: Can't access user manager.");
showError("Error: Could not access the User Manager. Is the system running?");
return;
}
-
+ Log.i(this, "onRun: parsing command.");
String command = nextArgRequired();
switch (command) {
case COMMAND_SET_PHONE_ACCOUNT_ENABLED:
@@ -143,6 +151,9 @@
case COMMAND_REGISTER_SIM_PHONE_ACCOUNT:
runRegisterSimPhoneAccount();
break;
+ case COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT:
+ runSetUserSelectedOutgoingPhoneAccount();
+ break;
case COMMAND_UNREGISTER_PHONE_ACCOUNT:
runUnregisterPhoneAccount();
break;
@@ -159,6 +170,7 @@
runWaitOnHandler();
break;
default:
+ Log.w(this, "onRun: unknown command: %s", command);
throw new IllegalArgumentException ("unknown command '" + command + "'");
}
}
@@ -227,6 +239,13 @@
mTelecomService.setTestPhoneAcctSuggestionComponent(componentName);
}
+ private void runSetUserSelectedOutgoingPhoneAccount() throws RemoteException {
+ Log.i(this, "runSetUserSelectedOutgoingPhoneAccount");
+ final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
+ mTelecomService.setUserSelectedOutgoingPhoneAccount(handle);
+ System.out.println("Success - " + handle + " set as default outgoing account.");
+ }
+
private void runUnregisterPhoneAccount() throws RemoteException {
final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
mTelecomService.unregisterPhoneAccount(handle);
@@ -256,7 +275,10 @@
}
- private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException{
+ private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException {
+ if (TextUtils.isEmpty(mArgs.peekNextArg())) {
+ return null;
+ }
final ComponentName component = parseComponentName(nextArgRequired());
final String accountId = nextArgRequired();
final String userSnInStr = nextArgRequired();
@@ -265,6 +287,7 @@
final int userSn = Integer.parseInt(userSnInStr);
userHandle = UserHandle.of(mUserManager.getUserHandle(userSn));
} catch (NumberFormatException ex) {
+ Log.w(this, "getPhoneAccountHandleFromArgs - invalid user %s", userSnInStr);
throw new IllegalArgumentException ("Invalid user serial number " + userSnInStr);
}
return new PhoneAccountHandle(component, accountId, userHandle);
@@ -277,4 +300,4 @@
}
return cn;
}
-}
\ No newline at end of file
+}
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 3ec0db4..c2e441b 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -2768,6 +2768,11 @@
HPLandroid/hardware/location/GeofenceHardwareService$1;->registerForMonitorStateChangeCallback(ILandroid/hardware/location/IGeofenceHardwareMonitorCallback;)Z
HPLandroid/hardware/location/GeofenceHardwareService$1;->removeGeofence(II)Z
HPLandroid/hardware/location/GeofenceHardwareService;->checkPermission(III)V
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub$Proxy;->onAvailabilityChanged(ZLandroid/hardware/location/IActivityRecognitionHardware;)V
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub;-><init>()V
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/location/IActivityRecognitionHardwareClient;
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub;->onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient;->onAvailabilityChanged(ZLandroid/hardware/location/IActivityRecognitionHardware;)V
HPLandroid/hardware/location/IContextHubCallback$Stub;->asBinder()Landroid/os/IBinder;
HPLandroid/hardware/location/IContextHubCallback$Stub;->onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
HPLandroid/hardware/location/IContextHubService$Stub$Proxy;->findNanoAppOnHub(ILandroid/hardware/location/NanoAppFilter;)[I
@@ -21777,6 +21782,7 @@
HSPLandroid/hardware/input/TouchCalibration$1;-><init>()V
HSPLandroid/hardware/input/TouchCalibration;-><init>()V
HSPLandroid/hardware/input/TouchCalibration;->getAffineTransform()[F
+HSPLandroid/hardware/location/ActivityRecognitionHardware;->isSupported()Z
HSPLandroid/hardware/location/ContextHubInfo$1;-><init>()V
HSPLandroid/hardware/location/ContextHubInfo;-><init>(Landroid/hardware/contexthub/V1_0/ContextHub;)V
HSPLandroid/hardware/location/ContextHubMessage$1;-><init>()V
@@ -21796,6 +21802,13 @@
HSPLandroid/hardware/location/GeofenceHardwareService;-><init>()V
HSPLandroid/hardware/location/GeofenceHardwareService;->onBind(Landroid/content/Intent;)Landroid/os/IBinder;
HSPLandroid/hardware/location/GeofenceHardwareService;->onCreate()V
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->disableActivityEvent(Ljava/lang/String;I)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->enableActivityEvent(Ljava/lang/String;IJ)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->flush()Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->getSupportedActivities()[Ljava/lang/String;
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->isActivitySupported(Ljava/lang/String;)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->registerSink(Landroid/hardware/location/IActivityRecognitionHardwareSink;)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->unregisterSink(Landroid/hardware/location/IActivityRecognitionHardwareSink;)Z
HSPLandroid/hardware/location/IContextHubCallback$Stub$Proxy;->asBinder()Landroid/os/IBinder;
HSPLandroid/hardware/location/IContextHubCallback$Stub$Proxy;->onMessageReceipt(IILandroid/hardware/location/ContextHubMessage;)V
HSPLandroid/hardware/location/IContextHubCallback;->onMessageReceipt(IILandroid/hardware/location/ContextHubMessage;)V
@@ -55637,6 +55650,7 @@
Landroid/hardware/input/KeyboardLayout$1;
Landroid/hardware/input/TouchCalibration$1;
Landroid/hardware/input/TouchCalibration;
+Landroid/hardware/location/ActivityRecognitionHardware;
Landroid/hardware/location/ContextHubInfo$1;
Landroid/hardware/location/ContextHubInfo;
Landroid/hardware/location/ContextHubManager;
@@ -55652,6 +55666,11 @@
Landroid/hardware/location/GeofenceHardwareRequestParcelable$1;
Landroid/hardware/location/GeofenceHardwareService$1;
Landroid/hardware/location/GeofenceHardwareService;
+Landroid/hardware/location/IActivityRecognitionHardware$Stub;
+Landroid/hardware/location/IActivityRecognitionHardware;
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub$Proxy;
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub;
+Landroid/hardware/location/IActivityRecognitionHardwareClient;
Landroid/hardware/location/IContextHubCallback$Stub$Proxy;
Landroid/hardware/location/IContextHubCallback;
Landroid/hardware/location/IContextHubClient$Stub;
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 700df64..ee2fe8f 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -462,6 +462,8 @@
Landroid/hardware/input/IInputManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/input/IInputManager;
Landroid/hardware/input/IInputManager$Stub;->TRANSACTION_injectInputEvent:I
Landroid/hardware/input/IInputManager;->injectInputEvent(Landroid/view/InputEvent;I)Z
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub;-><init>()V
+Landroid/hardware/location/IActivityRecognitionHardwareClient;->onAvailabilityChanged(ZLandroid/hardware/location/IActivityRecognitionHardware;)V
Landroid/hardware/location/IContextHubService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/location/IContextHubService;
Landroid/hardware/usb/IUsbManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
Landroid/hardware/usb/IUsbManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/usb/IUsbManager;
@@ -921,7 +923,6 @@
Landroid/os/PowerManager;->mService:Landroid/os/IPowerManager;
Landroid/os/PowerManager;->validateWakeLockParameters(ILjava/lang/String;)V
Landroid/os/PowerManager;->wakeUp(JLjava/lang/String;)V
-Landroid/os/Process;->BLUETOOTH_UID:I
Landroid/os/Process;->DRM_UID:I
Landroid/os/Process;->getFreeMemory()J
Landroid/os/Process;->getParentPid(I)I
@@ -948,10 +949,8 @@
Landroid/os/Process;->PROC_ZERO_TERM:I
Landroid/os/Process;->readProcFile(Ljava/lang/String;[I[Ljava/lang/String;[J[F)Z
Landroid/os/Process;->readProcLines(Ljava/lang/String;[Ljava/lang/String;[J)V
-Landroid/os/Process;->ROOT_UID:I
Landroid/os/Process;->setArgV0(Ljava/lang/String;)V
Landroid/os/Process;->setProcessGroup(II)V
-Landroid/os/Process;->SHELL_UID:I
Landroid/os/Process;->VPN_UID:I
Landroid/os/Process;->WIFI_UID:I
Landroid/os/RecoverySystem;->verifyPackageCompatibility(Ljava/io/InputStream;)Z
@@ -993,8 +992,6 @@
Landroid/os/ServiceManagerNative;->asInterface(Landroid/os/IBinder;)Landroid/os/IServiceManager;
Landroid/os/ServiceManagerProxy;->getService(Ljava/lang/String;)Landroid/os/IBinder;
Landroid/os/ServiceManagerProxy;->mRemote:Landroid/os/IBinder;
-Landroid/os/ServiceSpecificException;-><init>(ILjava/lang/String;)V
-Landroid/os/ServiceSpecificException;->errorCode:I
Landroid/os/SharedMemory;->getFd()I
Landroid/os/ShellCommand;->peekNextArg()Ljava/lang/String;
Landroid/os/StatFs;->mStat:Landroid/system/StructStatVfs;
@@ -3521,7 +3518,7 @@
Lcom/android/internal/telephony/SubscriptionController;->mLock:Ljava/lang/Object;
Lcom/android/internal/telephony/SubscriptionController;->notifySubscriptionInfoChanged()V
Lcom/android/internal/telephony/SubscriptionController;->setDefaultDataSubId(I)V
-Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(I)V
+Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(II)V
Lcom/android/internal/telephony/SubscriptionController;->setDefaultSmsSubId(I)V
Lcom/android/internal/telephony/SubscriptionController;->setDefaultVoiceSubId(I)V
Lcom/android/internal/telephony/SubscriptionController;->setPlmnSpn(IZLjava/lang/String;ZLjava/lang/String;)Z
diff --git a/config/preloaded-classes b/config/preloaded-classes
index cd798ad..c8a2a9c 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -1405,7 +1405,10 @@
android.hardware.input.InputManager$InputDeviceListener
android.hardware.input.InputManager$InputDeviceListenerDelegate
android.hardware.input.InputManager$InputDevicesChangedListener
+android.hardware.location.ActivityRecognitionHardware
android.hardware.location.ContextHubManager
+android.hardware.location.IActivityRecognitionHardware
+android.hardware.location.IActivityRecognitionHardware$Stub
android.hardware.radio.RadioManager
android.hardware.soundtrigger.SoundTrigger
android.hardware.soundtrigger.SoundTrigger$ConfidenceLevel
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 35098a0..cebe6e12 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -37,6 +37,7 @@
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.Display;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
@@ -382,7 +383,8 @@
void init(int connectionId, IBinder windowToken);
boolean onGesture(int gestureId);
boolean onKeyEvent(KeyEvent event);
- void onMagnificationChanged(@NonNull Region region,
+ /** Magnification changed callbacks for different displays */
+ void onMagnificationChanged(int displayId, @NonNull Region region,
float scale, float centerX, float centerY);
void onSoftKeyboardShowModeChanged(int showMode);
void onPerformGestureResult(int sequence, boolean completedSuccessfully);
@@ -452,7 +454,9 @@
private WindowManager mWindowManager;
- private MagnificationController mMagnificationController;
+ /** List of magnification controllers, mapping from displayId -> MagnificationController. */
+ private final SparseArray<MagnificationController> mMagnificationControllers =
+ new SparseArray<>(0);
private SoftKeyboardController mSoftKeyboardController;
private AccessibilityButtonController mAccessibilityButtonController;
@@ -483,8 +487,10 @@
* client code.
*/
private void dispatchServiceConnected() {
- if (mMagnificationController != null) {
- mMagnificationController.onServiceConnected();
+ synchronized (mLock) {
+ for (int i = 0; i < mMagnificationControllers.size(); i++) {
+ mMagnificationControllers.valueAt(i).onServiceConnectedLocked();
+ }
}
if (mSoftKeyboardController != null) {
mSoftKeyboardController.onServiceConnected();
@@ -652,11 +658,34 @@
*/
@NonNull
public final MagnificationController getMagnificationController() {
+ return getMagnificationController(Display.DEFAULT_DISPLAY);
+ }
+
+ /**
+ * Returns the magnification controller of specified logical display, which may be used to
+ * query and modify the state of display magnification.
+ * <p>
+ * <strong>Note:</strong> In order to control magnification, your service
+ * must declare the capability by setting the
+ * {@link android.R.styleable#AccessibilityService_canControlMagnification}
+ * property in its meta-data. For more information, see
+ * {@link #SERVICE_META_DATA}.
+ *
+ * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for
+ * default display.
+ * @return the magnification controller
+ *
+ * @hide
+ */
+ @NonNull
+ public final MagnificationController getMagnificationController(int displayId) {
synchronized (mLock) {
- if (mMagnificationController == null) {
- mMagnificationController = new MagnificationController(this, mLock);
+ MagnificationController controller = mMagnificationControllers.get(displayId);
+ if (controller == null) {
+ controller = new MagnificationController(this, mLock, displayId);
+ mMagnificationControllers.put(displayId, controller);
}
- return mMagnificationController;
+ return controller;
}
}
@@ -765,11 +794,14 @@
}
}
- private void onMagnificationChanged(@NonNull Region region, float scale,
+ private void onMagnificationChanged(int displayId, @NonNull Region region, float scale,
float centerX, float centerY) {
- if (mMagnificationController != null) {
- mMagnificationController.dispatchMagnificationChanged(
- region, scale, centerX, centerY);
+ MagnificationController controller;
+ synchronized (mLock) {
+ controller = mMagnificationControllers.get(displayId);
+ }
+ if (controller != null) {
+ controller.dispatchMagnificationChanged(region, scale, centerX, centerY);
}
}
@@ -794,6 +826,7 @@
*/
public static final class MagnificationController {
private final AccessibilityService mService;
+ private final int mDisplayId;
/**
* Map of listeners to their handlers. Lazily created when adding the
@@ -802,19 +835,19 @@
private ArrayMap<OnMagnificationChangedListener, Handler> mListeners;
private final Object mLock;
- MagnificationController(@NonNull AccessibilityService service, @NonNull Object lock) {
+ MagnificationController(@NonNull AccessibilityService service, @NonNull Object lock,
+ int displayId) {
mService = service;
mLock = lock;
+ mDisplayId = displayId;
}
/**
* Called when the service is connected.
*/
- void onServiceConnected() {
- synchronized (mLock) {
- if (mListeners != null && !mListeners.isEmpty()) {
- setMagnificationCallbackEnabled(true);
- }
+ void onServiceConnectedLocked() {
+ if (mListeners != null && !mListeners.isEmpty()) {
+ setMagnificationCallbackEnabled(true);
}
}
@@ -891,7 +924,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- connection.setMagnificationCallbackEnabled(enabled);
+ connection.setMagnificationCallbackEnabled(mDisplayId, enabled);
} catch (RemoteException re) {
throw new RuntimeException(re);
}
@@ -952,7 +985,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.getMagnificationScale();
+ return connection.getMagnificationScale(mDisplayId);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to obtain scale", re);
re.rethrowFromSystemServer();
@@ -981,7 +1014,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.getMagnificationCenterX();
+ return connection.getMagnificationCenterX(mDisplayId);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to obtain center X", re);
re.rethrowFromSystemServer();
@@ -1010,7 +1043,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.getMagnificationCenterY();
+ return connection.getMagnificationCenterY(mDisplayId);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to obtain center Y", re);
re.rethrowFromSystemServer();
@@ -1044,7 +1077,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.getMagnificationRegion();
+ return connection.getMagnificationRegion(mDisplayId);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to obtain magnified region", re);
re.rethrowFromSystemServer();
@@ -1073,7 +1106,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.resetMagnification(animate);
+ return connection.resetMagnification(mDisplayId, animate);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to reset", re);
re.rethrowFromSystemServer();
@@ -1101,7 +1134,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.setMagnificationScaleAndCenter(
+ return connection.setMagnificationScaleAndCenter(mDisplayId,
scale, Float.NaN, Float.NaN, animate);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to set scale", re);
@@ -1133,7 +1166,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.setMagnificationScaleAndCenter(
+ return connection.setMagnificationScaleAndCenter(mDisplayId,
Float.NaN, centerX, centerY, animate);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to set center", re);
@@ -1624,9 +1657,10 @@
}
@Override
- public void onMagnificationChanged(@NonNull Region region,
+ public void onMagnificationChanged(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
- AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY);
+ AccessibilityService.this.onMagnificationChanged(displayId, region, scale,
+ centerX, centerY);
}
@Override
@@ -1729,13 +1763,15 @@
mCaller.sendMessage(message);
}
- public void onMagnificationChanged(@NonNull Region region,
+ /** Magnification changed callbacks for different displays */
+ public void onMagnificationChanged(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
final SomeArgs args = SomeArgs.obtain();
args.arg1 = region;
args.arg2 = scale;
args.arg3 = centerX;
args.arg4 = centerY;
+ args.argi1 = displayId;
final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args);
mCaller.sendMessage(message);
@@ -1865,7 +1901,10 @@
final float scale = (float) args.arg2;
final float centerX = (float) args.arg3;
final float centerY = (float) args.arg4;
- mCallback.onMagnificationChanged(region, scale, centerX, centerY);
+ final int displayId = args.argi1;
+ args.recycle();
+ mCallback.onMagnificationChanged(displayId, region, scale,
+ centerX, centerY);
}
} return;
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 4e96b8f..1dae4fc 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -41,7 +41,7 @@
void onKeyEvent(in KeyEvent event, int sequence);
- void onMagnificationChanged(in Region region, float scale, float centerX, float centerY);
+ void onMagnificationChanged(int displayId, in Region region, float scale, float centerX, float centerY);
void onSoftKeyboardShowModeChanged(int showMode);
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 276131f..8c38fe4 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -70,20 +70,20 @@
oneway void setOnKeyEventResult(boolean handled, int sequence);
- float getMagnificationScale();
+ float getMagnificationScale(int displayId);
- float getMagnificationCenterX();
+ float getMagnificationCenterX(int displayId);
- float getMagnificationCenterY();
+ float getMagnificationCenterY(int displayId);
- Region getMagnificationRegion();
+ Region getMagnificationRegion(int displayId);
- boolean resetMagnification(boolean animate);
+ boolean resetMagnification(int displayId, boolean animate);
- boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+ boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX, float centerY,
boolean animate);
- void setMagnificationCallbackEnabled(boolean enabled);
+ void setMagnificationCallbackEnabled(int displayId, boolean enabled);
boolean setSoftKeyboardShowMode(int showMode);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 836627e..92f47e7 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -74,6 +74,7 @@
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.StrictMode;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.text.Selection;
import android.text.SpannableStringBuilder;
@@ -1049,33 +1050,56 @@
@Retention(RetentionPolicy.SOURCE)
@interface ContentCaptureNotificationType{}
-
- private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
- final ContentCaptureManager cm = getContentCaptureManager();
- if (cm == null) return;
-
+ private String getContentCaptureTypeAsString(@ContentCaptureNotificationType int type) {
switch (type) {
case CONTENT_CAPTURE_START:
- //TODO(b/111276913): decide whether the InteractionSessionId should be
- // saved / restored in the activity bundle - probably not
- int flags = 0;
- if ((getWindow().getAttributes().flags
- & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
- flags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
- }
- cm.onActivityStarted(mToken, getComponentName(), flags);
- break;
+ return "START";
case CONTENT_CAPTURE_PAUSE:
- cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_PAUSED);
- break;
+ return "PAUSE";
case CONTENT_CAPTURE_RESUME:
- cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_RESUMED);
- break;
+ return "RESUME";
case CONTENT_CAPTURE_STOP:
- cm.onActivityStopped();
- break;
+ return "STOP";
default:
- Log.wtf(TAG, "Invalid @ContentCaptureNotificationType: " + type);
+ return "UNKNOW-" + type;
+ }
+ }
+
+ private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "notifyContentCapture(" + getContentCaptureTypeAsString(type) + ") for "
+ + mComponent.toShortString());
+ }
+ try {
+ final ContentCaptureManager cm = getContentCaptureManager();
+ if (cm == null) return;
+
+ switch (type) {
+ case CONTENT_CAPTURE_START:
+ //TODO(b/111276913): decide whether the InteractionSessionId should be
+ // saved / restored in the activity bundle - probably not
+ int flags = 0;
+ if ((getWindow().getAttributes().flags
+ & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+ flags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
+ }
+ cm.onActivityStarted(mToken, getComponentName(), flags);
+ break;
+ case CONTENT_CAPTURE_PAUSE:
+ cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_PAUSED);
+ break;
+ case CONTENT_CAPTURE_RESUME:
+ cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_RESUMED);
+ break;
+ case CONTENT_CAPTURE_STOP:
+ cm.onActivityStopped();
+ break;
+ default:
+ Log.wtf(TAG, "Invalid @ContentCaptureNotificationType: " + type);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -8194,10 +8218,10 @@
final AutofillId autofillId = autofillIds[i];
final View view = autofillClientFindViewByAutofillIdTraversal(autofillId);
if (view != null) {
- if (!autofillId.isVirtual()) {
+ if (!autofillId.isVirtualInt()) {
visible[i] = view.isVisibleToUser();
} else {
- visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildId());
+ visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildIntId());
}
}
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e0b8d78..1045b7a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3529,12 +3529,32 @@
/**
* Returns "true" if device is running in a test harness.
+ *
+ * @deprecated this method is false for all user builds. Users looking to check if their device
+ * is running in a device farm should see {@link #isRunningInUserTestHarness()}.
*/
+ @Deprecated
public static boolean isRunningInTestHarness() {
return SystemProperties.getBoolean("ro.test_harness", false);
}
/**
+ * Returns "true" if the device is running in Test Harness Mode.
+ *
+ * <p>Test Harness Mode is a feature that allows devices to run without human interaction in a
+ * device farm/testing harness (such as Firebase Test Lab). You should check this method if you
+ * want your app to behave differently when running in a test harness to skip setup screens that
+ * would impede UI testing. e.g. a keyboard application that has a full screen setup page for
+ * the first time it is launched.
+ *
+ * <p>Note that you should <em>not</em> use this to determine whether or not your app is running
+ * an instrumentation test, as it is not set for a standard device running a test.
+ */
+ public static boolean isRunningInUserTestHarness() {
+ return SystemProperties.getBoolean("persist.sys.test_harness", false);
+ }
+
+ /**
* Unsupported compiled sdk warning should always be shown for the intput activity
* even in cases where the system would normally not show the warning. E.g. when running in a
* test harness.
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 5cac048..86e658d 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -219,9 +219,11 @@
* @param userId
* @param event
* @param appToken ActivityRecord's appToken.
+ * @param taskRoot TaskRecord's root
*/
public abstract void updateActivityUsageStats(
- ComponentName activity, int userId, int event, IBinder appToken);
+ ComponentName activity, int userId, int event, IBinder appToken,
+ ComponentName taskRoot);
public abstract void updateForegroundTimeIfOnBattery(
String packageName, int uid, long cpuTimeDiff);
public abstract void sendForegroundProfileChanged(int userId);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7767f04..ee3d27c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -77,9 +77,7 @@
import android.hardware.display.DisplayManagerGlobal;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
-import android.net.Network;
import android.net.Proxy;
-import android.net.ProxyInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
@@ -1033,15 +1031,10 @@
NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged();
}
- public void setHttpProxy(String host, String port, String exclList, Uri pacFileUrl) {
+ public void updateHttpProxy() {
final ConnectivityManager cm = ConnectivityManager.from(
getApplication() != null ? getApplication() : getSystemContext());
- final Network network = cm.getBoundNetworkForProcess();
- if (network != null) {
- Proxy.setHttpProxySystemProperty(cm.getDefaultProxy());
- } else {
- Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
- }
+ Proxy.setHttpProxySystemProperty(cm.getDefaultProxy());
}
public void processInBackground() {
@@ -5939,6 +5932,11 @@
Binder.enableTracing();
}
+ // Initialize heap profiling.
+ if (isAppProfileable || Build.IS_DEBUGGABLE) {
+ nInitZygoteChildHeapProfiling();
+ }
+
// Allow renderer debugging features if we're debuggable.
boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
HardwareRenderer.setDebuggingEnabled(isAppDebuggable || Build.IS_DEBUGGABLE);
@@ -5955,8 +5953,7 @@
// crash if we can't get it.
final IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
try {
- final ProxyInfo proxyInfo = service.getProxyForNetwork(null);
- Proxy.setHttpProxySystemProperty(proxyInfo);
+ Proxy.setHttpProxySystemProperty(service.getProxyForNetwork(null));
} catch (RemoteException e) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw e.rethrowFromSystemServer();
@@ -6965,4 +6962,5 @@
// ------------------ Regular JNI ------------------------
private native void nPurgePendingResources();
private native void nDumpGraphicsInfo(FileDescriptor fd);
+ private native void nInitZygoteChildHeapProfiling();
}
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index ab8f234..4d3711a 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -328,7 +328,7 @@
}
} else {
mTmpTransaction.reparent(mRootSurfaceControl,
- mSurfaceView.getSurfaceControl().getHandle()).apply();
+ mSurfaceView.getSurfaceControl()).apply();
}
if (mVirtualDisplay != null) {
@@ -390,7 +390,7 @@
.build();
try {
- wm.reparentDisplayContent(displayId, mRootSurfaceControl.getHandle());
+ wm.reparentDisplayContent(displayId, mRootSurfaceControl);
wm.dontOverrideDisplayInfo(displayId);
if (mSingleTaskInstance) {
mActivityTaskManager.setDisplayToSingleTaskInstance(displayId);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 5868771f..ab2430c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -27,6 +27,7 @@
import android.annotation.UnsupportedAppUsage;
import android.app.usage.UsageStatsManager;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.database.ContentObserver;
import android.media.AudioAttributes.AttributeUsage;
@@ -41,9 +42,9 @@
import android.os.UserManager;
import android.provider.Settings;
import android.util.ArrayMap;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import android.util.SparseArray;
import com.android.internal.app.IAppOpsActiveCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsNotedCallback;
@@ -60,8 +61,8 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
/**
@@ -1489,7 +1490,7 @@
AppOpsManager.MODE_ALLOWED, // READ_ICC_SMS
AppOpsManager.MODE_ALLOWED, // WRITE_ICC_SMS
AppOpsManager.MODE_DEFAULT, // WRITE_SETTINGS
- AppOpsManager.MODE_DEFAULT, // SYSTEM_ALERT_WINDOW
+ getSystemAlertWindowDefault(), // SYSTEM_ALERT_WINDOW
AppOpsManager.MODE_ALLOWED, // ACCESS_NOTIFICATIONS
AppOpsManager.MODE_ALLOWED, // CAMERA
AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO
@@ -4816,4 +4817,21 @@
}
}
}
+
+ private static int getSystemAlertWindowDefault() {
+ final Context context = ActivityThread.currentApplication();
+ if (context == null) {
+ return AppOpsManager.MODE_DEFAULT;
+ }
+
+ // system alert window is disable on low ram phones starting from Q
+ final PackageManager pm = context.getPackageManager();
+ // TVs are constantly plugged in and has less concern for memory/power
+ if (ActivityManager.isLowRamDeviceStatic()
+ && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK, 0)) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+
+ return AppOpsManager.MODE_DEFAULT;
+ }
}
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 1622c06..853b45e 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1324,7 +1324,8 @@
* @param description the description that would appear for this file in Downloads App.
* @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
* scanned by MediaScanner appear in the applications used to view media (for example,
- * Gallery app).
+ * Gallery app). Starting from {@link android.os.Build.VERSION_CODES#Q}, this argument is
+ * ignored and the file is always scanned by MediaScanner.
* @param mimeType mimetype of the file.
* @param path absolute pathname to the file. The file should be world-readable, so that it can
* be managed by the Downloads App and any other app that is used to read it (for example,
@@ -1353,7 +1354,8 @@
* @param description the description that would appear for this file in Downloads App.
* @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
* scanned by MediaScanner appear in the applications used to view media (for example,
- * Gallery app).
+ * Gallery app). Starting from {@link android.os.Build.VERSION_CODES#Q}, this argument is
+ * ignored and the file is always scanned by MediaScanner.
* @param mimeType mimetype of the file.
* @param path absolute pathname to the file. The file should be world-readable, so that it can
* be managed by the Downloads App and any other app that is used to read it (for example,
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index fcb6c14..c64fcf3 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -100,8 +100,7 @@
void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix,
in String[] args);
void clearDnsCache();
- void setHttpProxy(in String proxy, in String port, in String exclList,
- in Uri pacFileUrl);
+ void updateHttpProxy();
void setCoreSettings(in Bundle coreSettings);
void updatePackageCompatibilityInfo(in String pkg, in CompatibilityInfo info);
void scheduleTrimMemory(int level);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 1ae0f52..181acce 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -100,6 +101,12 @@
public static final String EXTRA_DESCRIPTION = "android.app.extra.DESCRIPTION";
/**
+ * A boolean value to forward to {@link android.hardware.biometrics.BiometricPrompt}.
+ * @hide
+ */
+ public static final String EXTRA_USE_IMPLICIT = "android.app.extra.USE_IMPLICIT";
+
+ /**
* A CharSequence description to show to the user on the alternate button when used with
* {@link #ACTION_CONFIRM_FRP_CREDENTIAL}.
* @hide
@@ -122,13 +129,39 @@
* {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
* {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
*
+ * @param title Title to be shown on the dialog.
+ * @param description Description to be shown on the dialog.
* @return the intent for launching the activity or null if no password is required.
**/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) {
- if (!isDeviceSecure()) return null;
+ return createConfirmDeviceCredentialIntent(title, description, false /* useImplicit */);
+ }
+
+ /**
+ * Get an intent to prompt the user to confirm credentials (pin, pattern or password)
+ * for the current user of the device. The caller is expected to launch this activity using
+ * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
+ * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
+ *
+ * @param title Title to be shown on the dialog.
+ * @param description Description to be shown on the dialog.
+ * @param useImplicit If useImplicit is set to true, ConfirmDeviceCredentials will invoke
+ * {@link android.hardware.biometrics.BiometricPrompt} with
+ * {@link android.hardware.biometrics.BiometricPrompt.Builder#setRequireConfirmation(
+ * boolean)} set to false.
+ * @return the intent for launching the activity or null if no password is required.
+ * @hide
+ */
+ public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description,
+ boolean useImplicit) {
+ if (!isDeviceSecure()) {
+ return null;
+ }
Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL);
intent.putExtra(EXTRA_TITLE, title);
intent.putExtra(EXTRA_DESCRIPTION, description);
+ intent.putExtra(EXTRA_USE_IMPLICIT, useImplicit);
// explicitly set the package for security
intent.setPackage(getSettingsPackageForIntent(intent));
@@ -176,6 +209,7 @@
* @throws IllegalStateException if the device has already been provisioned
* @hide
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
@SystemApi
public Intent createConfirmFactoryResetCredentialIntent(
CharSequence title, CharSequence description, CharSequence alternateButtonLabel) {
@@ -231,6 +265,7 @@
* secure notifications cannot be shown if {@code false}
* @hide
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
@RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)
@SystemApi
public void setPrivateNotificationsAllowed(boolean allow) {
@@ -249,6 +284,7 @@
* By default, private notifications are allowed.
* @hide
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
@RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)
@SystemApi
public boolean getPrivateNotificationsAllowed() {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 43614fe..c4b4b40 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1137,9 +1137,6 @@
}
}
- /**
- * @hide
- */
public boolean isNotificationAssistantAccessGranted(ComponentName assistant) {
INotificationManager service = getService();
try {
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 3f9627e..a021e3c 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1240,7 +1240,7 @@
}
@Override
- public void onMagnificationChanged(@NonNull Region region,
+ public void onMagnificationChanged(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
/* do nothing */
}
diff --git a/core/java/android/app/VrManager.java b/core/java/android/app/VrManager.java
index 5b87de4..5f1a94c 100644
--- a/core/java/android/app/VrManager.java
+++ b/core/java/android/app/VrManager.java
@@ -215,19 +215,12 @@
}
/**
- * Start VR Input method for the packageName in {@link ComponentName}.
- * This method notifies InputMethodManagerService to use VR IME instead of
- * regular phone IME.
- * @param componentName ComponentName of a VR InputMethod that should be set as selected
- * input by InputMethodManagerService.
+ * This method is not implemented.
+ *
+ * @param componentName not used
*/
@RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
public void setVrInputMethod(ComponentName componentName) {
- try {
- mService.setVrInputMethod(componentName);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
}
/**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6d7c547..ae756d4 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1362,16 +1362,23 @@
public static final String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
/**
- * Activity action: have the user enter a new password. This activity should
- * be launched after using {@link #setPasswordQuality(ComponentName, int)},
- * or {@link #setPasswordMinimumLength(ComponentName, int)} to have the user
- * enter a new password that meets the current requirements. You can use
- * {@link #isActivePasswordSufficient()} to determine whether you need to
- * have the user select a new password in order to meet the current
- * constraints. Upon being resumed from this activity, you can check the new
+ * Activity action: have the user enter a new password.
+ *
+ * <p>For admin apps, this activity should be launched after using {@link
+ * #setPasswordQuality(ComponentName, int)}, or {@link
+ * #setPasswordMinimumLength(ComponentName, int)} to have the user enter a new password that
+ * meets the current requirements. You can use {@link #isActivePasswordSufficient()} to
+ * determine whether you need to have the user select a new password in order to meet the
+ * current constraints. Upon being resumed from this activity, you can check the new
* password characteristics to see if they are sufficient.
*
- * If the intent is launched from within a managed profile with a profile
+ * <p>Non-admin apps can use {@link #getPasswordComplexity()} to check the current screen lock
+ * complexity, and use this activity with extra {@link #EXTRA_PASSWORD_COMPLEXITY} to suggest
+ * to users how complex the app wants the new screen lock to be. Note that both {@link
+ * #getPasswordComplexity()} and the extra {@link #EXTRA_PASSWORD_COMPLEXITY} require the
+ * calling app to have the permission {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY}.
+ *
+ * <p>If the intent is launched from within a managed profile with a profile
* owner built against {@link android.os.Build.VERSION_CODES#M} or before,
* this will trigger entering a new password for the parent of the profile.
* For all other cases it will trigger entering a new password for the user
@@ -1384,6 +1391,24 @@
= "android.app.action.SET_NEW_PASSWORD";
/**
+ * An integer indicating the complexity level of the new password an app would like the user to
+ * set when launching the action {@link #ACTION_SET_NEW_PASSWORD}.
+ *
+ * <p>Must be one of
+ * <ul>
+ * <li>{@link #PASSWORD_COMPLEXITY_HIGH}
+ * <li>{@link #PASSWORD_COMPLEXITY_MEDIUM}
+ * <li>{@link #PASSWORD_COMPLEXITY_LOW}
+ * <li>{@link #PASSWORD_COMPLEXITY_NONE}
+ * </ul>
+ *
+ * <p>If an invalid value is used, it will be treated as {@link #PASSWORD_COMPLEXITY_NONE}.
+ */
+ @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY)
+ public static final String EXTRA_PASSWORD_COMPLEXITY =
+ "android.app.extra.PASSWORD_COMPLEXITY";
+
+ /**
* Constant for {@link #getPasswordComplexity()}: no password.
*
* <p>Note that these complexity constants are ordered so that higher values are more complex.
@@ -2513,6 +2538,9 @@
* requested quality constant (between the policy set here, the user's preference, and any other
* considerations) is the one that is in effect.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -2548,6 +2576,9 @@
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
*
+ * <p>Note: on devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature,
+ * the password is always treated as empty.
+ *
* @param admin The name of the admin component to check, or {@code null} to aggregate
* all admins.
*/
@@ -2580,6 +2611,9 @@
* {@link #PASSWORD_QUALITY_ALPHANUMERIC}, or {@link #PASSWORD_QUALITY_COMPLEX} with
* {@link #setPasswordQuality}.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -2609,11 +2643,13 @@
* restrictions on this user and its participating profiles. Restrictions on profiles that have
* a separate challenge are not taken into account.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
*
- * user and its profiles or a particular one.
* @param admin The name of the admin component to check, or {@code null} to aggregate
* all admins.
*/
@@ -2644,6 +2680,9 @@
* setting this value. This constraint is only imposed if the administrator has also requested
* {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -2678,6 +2717,9 @@
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
@@ -2714,6 +2756,9 @@
* setting this value. This constraint is only imposed if the administrator has also requested
* {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -2748,6 +2793,9 @@
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
@@ -2784,6 +2832,9 @@
* only imposed if the administrator has also requested {@link #PASSWORD_QUALITY_COMPLEX} with
* {@link #setPasswordQuality}. The default value is 1.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -2818,6 +2869,9 @@
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
@@ -2853,6 +2907,9 @@
* setting this value. This constraint is only imposed if the administrator has also requested
* {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 1.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -2887,6 +2944,9 @@
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
@@ -2922,6 +2982,9 @@
* only imposed if the administrator has also requested {@link #PASSWORD_QUALITY_COMPLEX} with
* {@link #setPasswordQuality}. The default value is 1.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -2955,6 +3018,9 @@
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
@@ -2990,6 +3056,9 @@
* setting this value. This constraint is only imposed if the administrator has also requested
* {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -3024,6 +3093,9 @@
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
@@ -3060,6 +3132,9 @@
* , {@link #PASSWORD_QUALITY_NUMERIC_COMPLEX} {@link #PASSWORD_QUALITY_ALPHABETIC}, or
* {@link #PASSWORD_QUALITY_ALPHANUMERIC} with {@link #setPasswordQuality}.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -3112,6 +3187,7 @@
* @throws SecurityException if {@code admin} is not an active administrator or {@code admin}
* does not use {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD}
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void setPasswordExpirationTimeout(@NonNull ComponentName admin, long timeout) {
if (mService != null) {
try {
@@ -3136,6 +3212,7 @@
* @param admin The name of the admin component to check, or {@code null} to aggregate all admins.
* @return The timeout for the given admin or the minimum of all timeouts
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public long getPasswordExpirationTimeout(@Nullable ComponentName admin) {
if (mService != null) {
try {
@@ -3160,6 +3237,7 @@
* @param admin The name of the admin component to check, or {@code null} to aggregate all admins.
* @return The password expiration time, in milliseconds since epoch.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public long getPasswordExpiration(@Nullable ComponentName admin) {
if (mService != null) {
try {
@@ -3184,12 +3262,14 @@
* all admins.
* @return The length of the password history
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public int getPasswordHistoryLength(@Nullable ComponentName admin) {
return getPasswordHistoryLength(admin, myUserId());
}
/** @hide per-user version */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public int getPasswordHistoryLength(@Nullable ComponentName admin, int userHandle) {
if (mService != null) {
try {
@@ -3204,10 +3284,16 @@
/**
* Return the maximum password length that the device supports for a
* particular password quality.
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always empty.
* @param quality The quality being interrogated.
* @return Returns the maximum length that the user can enter.
*/
public int getPasswordMaximumLength(int quality) {
+ PackageManager pm = mContext.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ID_ATTESTATION)) {
+ return 0;
+ }
// Kind-of arbitrary.
return 16;
}
@@ -3218,6 +3304,10 @@
* user and its participating profiles. Restrictions on profiles that have a separate challenge
* are not taken into account. The user must be unlocked in order to perform the check.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty - i.e. this method will always return false on such
+ * devices, provided any password requirements were set.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -3250,6 +3340,9 @@
* explicitly querying the parent profile screen lock complexity via {@link
* #getParentProfileInstance}.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* @throws IllegalStateException if the user is not unlocked.
* @throws SecurityException if the calling application does not have the permission
* {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY}
@@ -3329,6 +3422,7 @@
* @throws SecurityException if the calling application does not own an active administrator
* that uses {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN}
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public int getCurrentFailedPasswordAttempts() {
return getCurrentFailedPasswordAttempts(myUserId());
}
@@ -3396,6 +3490,7 @@
* both {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and
* {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void setMaximumFailedPasswordsForWipe(@NonNull ComponentName admin, int num) {
if (mService != null) {
try {
@@ -3419,12 +3514,14 @@
* @param admin The name of the admin component to check, or {@code null} to aggregate
* all admins.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public int getMaximumFailedPasswordsForWipe(@Nullable ComponentName admin) {
return getMaximumFailedPasswordsForWipe(admin, myUserId());
}
/** @hide per-user version */
@UnsupportedAppUsage
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public int getMaximumFailedPasswordsForWipe(@Nullable ComponentName admin, int userHandle) {
if (mService != null) {
try {
@@ -3444,6 +3541,7 @@
* user passed in.
* @hide Used only by Keyguard
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle) {
if (mService != null) {
try {
@@ -3514,6 +3612,7 @@
* that uses {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD}
* @throws IllegalStateException if the calling user is locked or has a managed profile.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public boolean resetPassword(String password, int flags) {
throwIfParentInstance("resetPassword");
if (mService != null) {
@@ -3557,6 +3656,7 @@
* @throws SecurityException if admin is not a device or profile owner.
* @throws IllegalArgumentException if the supplied token is invalid.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public boolean setResetPasswordToken(ComponentName admin, byte[] token) {
throwIfParentInstance("setResetPasswordToken");
if (mService != null) {
@@ -3576,6 +3676,7 @@
* @return true if the operation is successful, false otherwise.
* @throws SecurityException if admin is not a device or profile owner.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public boolean clearResetPasswordToken(ComponentName admin) {
throwIfParentInstance("clearResetPasswordToken");
if (mService != null) {
@@ -3596,6 +3697,7 @@
* @throws SecurityException if admin is not a device or profile owner.
* @throws IllegalStateException if no token has been set.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public boolean isResetPasswordTokenActive(ComponentName admin) {
throwIfParentInstance("isResetPasswordTokenActive");
if (mService != null) {
@@ -3637,6 +3739,7 @@
* @throws SecurityException if admin is not a device or profile owner.
* @throws IllegalStateException if the provided token is not valid.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public boolean resetPasswordWithToken(@NonNull ComponentName admin, String password,
byte[] token, int flags) {
throwIfParentInstance("resetPassword");
@@ -3742,6 +3845,7 @@
*
* @throws SecurityException if {@code admin} is not a device or profile owner.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void setRequiredStrongAuthTimeout(@NonNull ComponentName admin,
long timeoutMs) {
if (mService != null) {
@@ -3766,12 +3870,14 @@
* across all participating admins.
* @return The timeout in milliseconds or 0 if not configured for the provided admin.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin) {
return getRequiredStrongAuthTimeout(admin, myUserId());
}
/** @hide per-user version */
@UnsupportedAppUsage
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin, @UserIdInt int userId) {
if (mService != null) {
try {
@@ -5350,6 +5456,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
if (mService != null) {
try {
@@ -5363,6 +5470,7 @@
/**
* @hide
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void reportPasswordChanged(@UserIdInt int userId) {
if (mService != null) {
try {
@@ -5377,6 +5485,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void reportFailedPasswordAttempt(int userHandle) {
if (mService != null) {
try {
@@ -5391,6 +5500,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void reportSuccessfulPasswordAttempt(int userHandle) {
if (mService != null) {
try {
@@ -5404,6 +5514,7 @@
/**
* @hide
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void reportFailedBiometricAttempt(int userHandle) {
if (mService != null) {
try {
@@ -5417,6 +5528,7 @@
/**
* @hide
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void reportSuccessfulBiometricAttempt(int userHandle) {
if (mService != null) {
try {
@@ -6383,6 +6495,7 @@
* @throws SecurityException if {@code admin} is not an active administrator or does not use
* {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES}
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void setTrustAgentConfiguration(@NonNull ComponentName admin,
@NonNull ComponentName target, PersistableBundle configuration) {
if (mService != null) {
@@ -6412,6 +6525,7 @@
* @param agent Which component to get enabled features for.
* @return configuration for the given trust agent.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public @Nullable List<PersistableBundle> getTrustAgentConfiguration(
@Nullable ComponentName admin, @NonNull ComponentName agent) {
return getTrustAgentConfiguration(admin, agent, myUserId());
@@ -6419,6 +6533,7 @@
/** @hide per-user version */
@UnsupportedAppUsage
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public @Nullable List<PersistableBundle> getTrustAgentConfiguration(
@Nullable ComponentName admin, @NonNull ComponentName agent, int userHandle) {
if (mService != null) {
@@ -10313,23 +10428,28 @@
}
/**
- * Whitelists a package that is allowed to access cross profile calendar APIs.
+ * Whitelists a set of packages that are allowed to access cross-profile calendar APIs.
*
* <p>Called by a profile owner of a managed profile.
*
+ * <p>Calling with a null value for the set disables the restriction so that all packages
+ * are allowed to access cross-profile calendar APIs. Calling with an empty set disallows
+ * all packages from accessing cross-profile calendar APIs. If this method isn't called,
+ * no package will be allowed to access cross-profile calendar APIs by default.
+ *
* @param admin which {@link DeviceAdminReceiver} this request is associated with.
- * @param packageName name of the package to be whitelisted.
+ * @param packageNames set of packages to be whitelisted.
* @throws SecurityException if {@code admin} is not a profile owner.
*
- * @see #removeCrossProfileCalendarPackage(ComponentName, String)
* @see #getCrossProfileCalendarPackages(ComponentName)
*/
- public void addCrossProfileCalendarPackage(@NonNull ComponentName admin,
- @NonNull String packageName) {
- throwIfParentInstance("addCrossProfileCalendarPackage");
+ public void setCrossProfileCalendarPackages(@NonNull ComponentName admin,
+ @Nullable Set<String> packageNames) {
+ throwIfParentInstance("setCrossProfileCalendarPackages");
if (mService != null) {
try {
- mService.addCrossProfileCalendarPackage(admin, packageName);
+ mService.setCrossProfileCalendarPackages(admin, packageNames == null ? null
+ : new ArrayList<>(packageNames));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -10337,52 +10457,24 @@
}
/**
- * Removes a package that was allowed to access cross profile calendar APIs
- * from the whitelist.
- *
- * <p>Called by a profile owner of a managed profile.
- *
- * @param admin which {@link DeviceAdminReceiver} this request is associated with.
- * @param packageName name of the package to be removed from the whitelist.
- * @return {@code true} if the package is successfully removed from the whitelist,
- * {@code false} otherwise.
- * @throws SecurityException if {@code admin} is not a profile owner.
- *
- * @see #addCrossProfileCalendarPackage(ComponentName, String)
- * @see #getCrossProfileCalendarPackages(ComponentName)
- */
- public boolean removeCrossProfileCalendarPackage(@NonNull ComponentName admin,
- @NonNull String packageName) {
- throwIfParentInstance("removeCrossProfileCalendarPackage");
- if (mService != null) {
- try {
- return mService.removeCrossProfileCalendarPackage(admin, packageName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- return false;
- }
-
- /**
- * Gets a set of package names that are whitelisted to access cross profile calendar APIs.
+ * Gets a set of package names that are whitelisted to access cross-profile calendar APIs.
*
* <p>Called by a profile owner of a managed profile.
*
* @param admin which {@link DeviceAdminReceiver} this request is associated with.
* @return the set of names of packages that were previously whitelisted via
- * {@link #addCrossProfileCalendarPackage(ComponentName, String)}, or an
+ * {@link #setCrossProfileCalendarPackages(ComponentName, Set)}, or an
* empty set if none have been whitelisted.
* @throws SecurityException if {@code admin} is not a profile owner.
*
- * @see #addCrossProfileCalendarPackage(ComponentName, String)
- * @see #removeCrossProfileCalendarPackage(ComponentName, String)
+ * @see #setCrossProfileCalendarPackages(ComponentName, Set)
*/
- public @NonNull Set<String> getCrossProfileCalendarPackages(@NonNull ComponentName admin) {
+ public @Nullable Set<String> getCrossProfileCalendarPackages(@NonNull ComponentName admin) {
throwIfParentInstance("getCrossProfileCalendarPackages");
if (mService != null) {
try {
- return new ArraySet<>(mService.getCrossProfileCalendarPackages(admin));
+ final List<String> packageNames = mService.getCrossProfileCalendarPackages(admin);
+ return packageNames == null ? null : new ArraySet<>(packageNames);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -10391,22 +10483,21 @@
}
/**
- * Returns if a package is whitelisted to access cross profile calendar APIs.
+ * Returns if a package is whitelisted to access cross-profile calendar APIs.
*
* <p>To query for a specific user, use
* {@link Context#createPackageContextAsUser(String, int, UserHandle)} to create a context for
* that user, and get a {@link DevicePolicyManager} from this context.
*
* @param packageName the name of the package
- * @return {@code true} if the package is whitelisted to access cross profile calendar APIs.
+ * @return {@code true} if the package is whitelisted to access cross-profile calendar APIs.
* {@code false} otherwise.
*
- * @see #addCrossProfileCalendarPackage(ComponentName, String)
- * @see #removeCrossProfileCalendarPackage(ComponentName, String)
+ * @see #setCrossProfileCalendarPackages(ComponentName, Set)
* @see #getCrossProfileCalendarPackages(ComponentName)
* @hide
*/
- public @NonNull boolean isPackageAllowedToAccessCalendar(@NonNull String packageName) {
+ public boolean isPackageAllowedToAccessCalendar(@NonNull String packageName) {
throwIfParentInstance("isPackageAllowedToAccessCalendar");
if (mService != null) {
try {
@@ -10420,27 +10511,27 @@
}
/**
- * Gets a set of package names that are whitelisted to access cross profile calendar APIs.
+ * Gets a set of package names that are whitelisted to access cross-profile calendar APIs.
*
* <p>To query for a specific user, use
* {@link Context#createPackageContextAsUser(String, int, UserHandle)} to create a context for
* that user, and get a {@link DevicePolicyManager} from this context.
*
* @return the set of names of packages that were previously whitelisted via
- * {@link #addCrossProfileCalendarPackage(ComponentName, String)}, or an
+ * {@link #setCrossProfileCalendarPackages(ComponentName, Set)}, or an
* empty set if none have been whitelisted.
*
- * @see #addCrossProfileCalendarPackage(ComponentName, String)
- * @see #removeCrossProfileCalendarPackage(ComponentName, String)
+ * @see #setCrossProfileCalendarPackages(ComponentName, Set)
* @see #getCrossProfileCalendarPackages(ComponentName)
* @hide
*/
- public @NonNull Set<String> getCrossProfileCalendarPackages() {
+ public @Nullable Set<String> getCrossProfileCalendarPackages() {
throwIfParentInstance("getCrossProfileCalendarPackages");
if (mService != null) {
try {
- return new ArraySet<>(mService.getCrossProfileCalendarPackagesForUser(
- myUserId()));
+ final List<String> packageNames = mService.getCrossProfileCalendarPackagesForUser(
+ myUserId());
+ return packageNames == null ? null : new ArraySet<>(packageNames);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 568becf..1751a91c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -424,8 +424,7 @@
void installUpdateFromFile(in ComponentName admin, in ParcelFileDescriptor updateFileDescriptor, in StartInstallingUpdateCallback listener);
- void addCrossProfileCalendarPackage(in ComponentName admin, String packageName);
- boolean removeCrossProfileCalendarPackage(in ComponentName admin, String packageName);
+ void setCrossProfileCalendarPackages(in ComponentName admin, in List<String> packageNames);
List<String> getCrossProfileCalendarPackages(in ComponentName admin);
boolean isPackageAllowedToAccessCalendarForUser(String packageName, int userHandle);
List<String> getCrossProfileCalendarPackagesForUser(int userHandle);
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index 8b41755..e5df2c7 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -20,6 +20,11 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -27,6 +32,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -85,6 +92,101 @@
nonLetter = in.readInt();
}
+ /** Returns the min quality allowed by {@code complexityLevel}. */
+ public static int complexityLevelToMinQuality(@PasswordComplexity int complexityLevel) {
+ // this would be the quality of the first metrics since mMetrics is sorted in ascending
+ // order of quality
+ return PasswordComplexityBucket
+ .complexityLevelToBucket(complexityLevel).mMetrics[0].quality;
+ }
+
+ /**
+ * Returns a merged minimum {@link PasswordMetrics} requirements that a new password must meet
+ * to fulfil {@code requestedQuality}, {@code requiresNumeric} and {@code
+ * requiresLettersOrSymbols}, which are derived from {@link DevicePolicyManager} requirements,
+ * and {@code complexityLevel}.
+ *
+ * <p>Note that we are taking {@code userEnteredPasswordQuality} into account because there are
+ * more than one set of metrics to meet the minimum complexity requirement and inspecting what
+ * the user has entered can help determine whether the alphabetic or alphanumeric set of metrics
+ * should be used. For example, suppose minimum complexity requires either ALPHABETIC(8+), or
+ * ALPHANUMERIC(6+). If the user has entered "a", the length requirement displayed on the UI
+ * would be 8. Then the user appends "1" to make it "a1". We now know the user is entering
+ * an alphanumeric password so we would update the min complexity required min length to 6.
+ */
+ public static PasswordMetrics getMinimumMetrics(@PasswordComplexity int complexityLevel,
+ int userEnteredPasswordQuality, int requestedQuality, boolean requiresNumeric,
+ boolean requiresLettersOrSymbols) {
+ int targetQuality = Math.max(
+ userEnteredPasswordQuality,
+ getActualRequiredQuality(
+ requestedQuality, requiresNumeric, requiresLettersOrSymbols));
+ return getTargetQualityMetrics(complexityLevel, targetQuality);
+ }
+
+ /**
+ * Returns the {@link PasswordMetrics} at {@code complexityLevel} which the metrics quality
+ * is the same as {@code targetQuality}.
+ *
+ * <p>If {@code complexityLevel} does not allow {@code targetQuality}, returns the metrics
+ * with the min quality at {@code complexityLevel}.
+ */
+ // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
+ @VisibleForTesting
+ public static PasswordMetrics getTargetQualityMetrics(
+ @PasswordComplexity int complexityLevel, int targetQuality) {
+ PasswordComplexityBucket targetBucket =
+ PasswordComplexityBucket.complexityLevelToBucket(complexityLevel);
+ for (PasswordMetrics metrics : targetBucket.mMetrics) {
+ if (targetQuality == metrics.quality) {
+ return metrics;
+ }
+ }
+ // none of the metrics at complexityLevel has targetQuality, return metrics with min quality
+ // see test case testGetMinimumMetrics_actualRequiredQualityStricter for an example, where
+ // min complexity allows at least NUMERIC_COMPLEX, user has not entered anything yet, and
+ // requested quality is NUMERIC
+ return targetBucket.mMetrics[0];
+ }
+
+ /**
+ * Finds out the actual quality requirement based on whether quality is {@link
+ * DevicePolicyManager#PASSWORD_QUALITY_COMPLEX} and whether digits, letters or symbols are
+ * required.
+ */
+ @VisibleForTesting
+ // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
+ public static int getActualRequiredQuality(
+ int requestedQuality, boolean requiresNumeric, boolean requiresLettersOrSymbols) {
+ if (requestedQuality != PASSWORD_QUALITY_COMPLEX) {
+ return requestedQuality;
+ }
+
+ // find out actual password quality from complex requirements
+ if (requiresNumeric && requiresLettersOrSymbols) {
+ return PASSWORD_QUALITY_ALPHANUMERIC;
+ }
+ if (requiresLettersOrSymbols) {
+ return PASSWORD_QUALITY_ALPHABETIC;
+ }
+ if (requiresNumeric) {
+ // cannot specify numeric complex using complex quality so this must be numeric
+ return PASSWORD_QUALITY_NUMERIC;
+ }
+
+ // reaching here means dpm sets quality to complex without specifying any requirements
+ return PASSWORD_QUALITY_UNSPECIFIED;
+ }
+
+ /**
+ * Returns {@code complexityLevel} or {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}
+ * if {@code complexityLevel} is not valid.
+ */
+ @PasswordComplexity
+ public static int sanitizeComplexityLevel(@PasswordComplexity int complexityLevel) {
+ return PasswordComplexityBucket.complexityLevelToBucket(complexityLevel).mComplexityLevel;
+ }
+
public boolean isDefault() {
return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
&& length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0
@@ -280,7 +382,7 @@
@PasswordComplexity
public int determineComplexity() {
for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) {
- if (satisfiesBucket(bucket.getMetrics())) {
+ if (satisfiesBucket(bucket.mMetrics)) {
return bucket.mComplexityLevel;
}
}
@@ -290,7 +392,7 @@
/**
* Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}.
*/
- public static class PasswordComplexityBucket {
+ private static class PasswordComplexityBucket {
/**
* Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of
* {@link PasswordMetrics}.
@@ -299,12 +401,13 @@
new PasswordComplexityBucket(
PASSWORD_COMPLEXITY_HIGH,
new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 6),
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+ 8),
new PasswordMetrics(
DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6),
new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
- 8));
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
+ 6));
/**
* Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of
@@ -314,11 +417,12 @@
new PasswordComplexityBucket(
PASSWORD_COMPLEXITY_MEDIUM,
new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 4),
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+ 4),
new PasswordMetrics(
DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4),
new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
4));
/**
@@ -328,11 +432,11 @@
private static final PasswordComplexityBucket LOW =
new PasswordComplexityBucket(
PASSWORD_COMPLEXITY_LOW,
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING),
new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING));
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC));
/**
* A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}.
@@ -348,19 +452,27 @@
private final int mComplexityLevel;
private final PasswordMetrics[] mMetrics;
+ /**
+ * @param metricsArray must be sorted in ascending order of {@link #quality}.
+ */
private PasswordComplexityBucket(@PasswordComplexity int complexityLevel,
- PasswordMetrics... metrics) {
- this.mComplexityLevel = complexityLevel;
- this.mMetrics = metrics;
- }
+ PasswordMetrics... metricsArray) {
+ int previousQuality = PASSWORD_QUALITY_UNSPECIFIED;
+ for (PasswordMetrics metrics : metricsArray) {
+ if (metrics.quality < previousQuality) {
+ throw new IllegalArgumentException("metricsArray must be sorted in ascending"
+ + " order of quality");
+ }
+ previousQuality = metrics.quality;
+ }
- /** Returns the {@link PasswordMetrics} that meet the min requirements of this bucket. */
- public PasswordMetrics[] getMetrics() {
- return mMetrics;
+ this.mMetrics = metricsArray;
+ this.mComplexityLevel = complexityLevel;
+
}
/** Returns the bucket that {@code complexityLevel} represents. */
- public static PasswordComplexityBucket complexityLevelToBucket(
+ private static PasswordComplexityBucket complexityLevelToBucket(
@PasswordComplexity int complexityLevel) {
for (PasswordComplexityBucket bucket : BUCKETS) {
if (bucket.mComplexityLevel == complexityLevel) {
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 7d03f00..6006ad2 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -894,7 +894,7 @@
}
if (mAutofillId != null) {
autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID;
- if (mAutofillId.isVirtual()) {
+ if (mAutofillId.isVirtualInt()) {
autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID;
}
}
@@ -961,8 +961,9 @@
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) {
out.writeInt(mAutofillId.getViewId());
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID) != 0) {
- out.writeInt(mAutofillId.getVirtualChildId());
+ out.writeInt(mAutofillId.getVirtualChildIntId());
}
+ // TODO(b/113593220): write session id as well
}
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_TYPE) != 0) {
out.writeInt(mAutofillType);
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index c983d4f..24580b4 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -16,6 +16,7 @@
package android.app.backup;
+import android.annotation.Nullable;
import android.app.IBackupAgent;
import android.app.QueuedWork;
import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
@@ -181,6 +182,8 @@
Handler mHandler = null;
+ @Nullable private UserHandle mUser;
+
Handler getHandler() {
if (mHandler == null) {
mHandler = new Handler(Looper.getMainLooper());
@@ -232,6 +235,8 @@
*/
public void onCreate(UserHandle user) {
onCreate();
+
+ mUser = user;
}
/**
@@ -528,6 +533,10 @@
public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
}
+ private int getBackupUserId() {
+ return mUser == null ? super.getUserId() : mUser.getIdentifier();
+ }
+
/**
* Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>.
* If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path
@@ -1033,7 +1042,7 @@
Binder.restoreCallingIdentity(ident);
try {
- callbackBinder.opComplete(token, 0);
+ callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
@@ -1082,7 +1091,7 @@
Binder.restoreCallingIdentity(ident);
try {
- callbackBinder.opComplete(token, 0);
+ callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
@@ -1112,7 +1121,8 @@
} finally {
Binder.restoreCallingIdentity(ident);
try {
- callbackBinder.opComplete(token, measureOutput.getSize());
+ callbackBinder.opCompleteForUser(getBackupUserId(), token,
+ measureOutput.getSize());
} catch (RemoteException e) {
// timeout, so we're safe
}
@@ -1137,7 +1147,7 @@
Binder.restoreCallingIdentity(ident);
try {
- callbackBinder.opComplete(token, 0);
+ callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
@@ -1162,7 +1172,7 @@
Binder.restoreCallingIdentity(ident);
try {
- callbackBinder.opComplete(token, 0);
+ callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index f8c5a81..eda8981 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -549,6 +549,19 @@
/**
* Notify the backup manager that a BackupAgent has completed the operation
+ * corresponding to the given token and user id.
+ *
+ * @param userId User id for which the operation has been completed.
+ * @param token The transaction token passed to the BackupAgent method being
+ * invoked.
+ * @param result In the case of a full backup measure operation, the estimated
+ * total file size that would result from the operation. Unused in all other
+ * cases.
+ */
+ void opCompleteForUser(int userId, int token, long result);
+
+ /**
+ * Notify the backup manager that a BackupAgent has completed the operation
* corresponding to the given token.
*
* @param token The transaction token passed to the BackupAgent method being
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index bbae7d3..b1500c1 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -55,8 +55,13 @@
long sessionThresholdTimeMs, in PendingIntent limitReachedCallbackIntent,
in PendingIntent sessionEndCallbackIntent, String callingPackage);
void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage);
+ void registerAppUsageLimitObserver(int observerId, in String[] packages, long timeLimitMs,
+ in PendingIntent callback, String callingPackage);
+ void unregisterAppUsageLimitObserver(int observerId, String callingPackage);
void reportUsageStart(in IBinder activity, String token, String callingPackage);
void reportPastUsageStart(in IBinder activity, String token, long timeAgoMs,
String callingPackage);
void reportUsageStop(in IBinder activity, String token, String callingPackage);
+ int getUsageSource();
+ void forceUsageSourceSettingRead();
}
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 2c5fe04..451f44b 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -16,6 +16,7 @@
package android.app.usage;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.content.res.Configuration;
@@ -286,7 +287,6 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public String mClass;
-
/**
* {@hide}
*/
@@ -295,6 +295,16 @@
/**
* {@hide}
*/
+ public String mTaskRootPackage;
+
+ /**
+ * {@hide}
+ */
+ public String mTaskRootClass;
+
+ /**
+ * {@hide}
+ */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public long mTimeStamp;
@@ -373,6 +383,8 @@
mPackage = orig.mPackage;
mClass = orig.mClass;
mInstanceId = orig.mInstanceId;
+ mTaskRootPackage = orig.mTaskRootPackage;
+ mTaskRootClass = orig.mTaskRootClass;
mTimeStamp = orig.mTimeStamp;
mEventType = orig.mEventType;
mConfiguration = orig.mConfiguration;
@@ -411,6 +423,28 @@
}
/**
+ * The package name of the task root when this event was reported.
+ * Or {@code null} for queries from apps without {@link
+ * android.Manifest.permission#PACKAGE_USAGE_STATS}
+ * @hide
+ */
+ @SystemApi
+ public @Nullable String getTaskRootPackageName() {
+ return mTaskRootPackage;
+ }
+
+ /**
+ * The class name of the task root when this event was reported.
+ * Or {@code null} for queries from apps without {@link
+ * android.Manifest.permission#PACKAGE_USAGE_STATS}
+ * @hide
+ */
+ @SystemApi
+ public @Nullable String getTaskRootClassName() {
+ return mTaskRootClass;
+ }
+
+ /**
* The time at which this event occurred, measured in milliseconds since the epoch.
* <p/>
* See {@link System#currentTimeMillis()}.
@@ -522,6 +556,9 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private int mIndex = 0;
+ // Only used when parceling events. If false, task roots will be omitted from the parcel
+ private final boolean mIncludeTaskRoots;
+
/*
* In order to save space, since ComponentNames will be duplicated everywhere,
* we use a map and index into it.
@@ -552,6 +589,7 @@
mParcel.setDataSize(mParcel.dataPosition());
mParcel.setDataPosition(positionInParcel);
}
+ mIncludeTaskRoots = true;
}
/**
@@ -560,16 +598,27 @@
*/
UsageEvents() {
mEventCount = 0;
+ mIncludeTaskRoots = true;
+ }
+
+ /**
+ * Construct the iterator in preparation for writing it to a parcel.
+ * Defaults to excluding task roots from the parcel.
+ * {@hide}
+ */
+ public UsageEvents(List<Event> events, String[] stringPool) {
+ this(events, stringPool, false);
}
/**
* Construct the iterator in preparation for writing it to a parcel.
* {@hide}
*/
- public UsageEvents(List<Event> events, String[] stringPool) {
+ public UsageEvents(List<Event> events, String[] stringPool, boolean includeTaskRoots) {
mStringPool = stringPool;
mEventCount = events.size();
mEventsToWrite = events;
+ mIncludeTaskRoots = includeTaskRoots;
}
/**
@@ -645,9 +694,25 @@
} else {
classIndex = -1;
}
+
+ final int taskRootPackageIndex;
+ if (mIncludeTaskRoots && event.mTaskRootPackage != null) {
+ taskRootPackageIndex = findStringIndex(event.mTaskRootPackage);
+ } else {
+ taskRootPackageIndex = -1;
+ }
+
+ final int taskRootClassIndex;
+ if (mIncludeTaskRoots && event.mTaskRootClass != null) {
+ taskRootClassIndex = findStringIndex(event.mTaskRootClass);
+ } else {
+ taskRootClassIndex = -1;
+ }
p.writeInt(packageIndex);
p.writeInt(classIndex);
p.writeInt(event.mInstanceId);
+ p.writeInt(taskRootPackageIndex);
+ p.writeInt(taskRootClassIndex);
p.writeInt(event.mEventType);
p.writeLong(event.mTimeStamp);
@@ -691,6 +756,21 @@
eventOut.mClass = null;
}
eventOut.mInstanceId = p.readInt();
+
+ final int taskRootPackageIndex = p.readInt();
+ if (taskRootPackageIndex >= 0) {
+ eventOut.mTaskRootPackage = mStringPool[taskRootPackageIndex];
+ } else {
+ eventOut.mTaskRootPackage = null;
+ }
+
+ final int taskRootClassIndex = p.readInt();
+ if (taskRootClassIndex >= 0) {
+ eventOut.mTaskRootClass = mStringPool[taskRootClassIndex];
+ } else {
+ eventOut.mTaskRootClass = null;
+ }
+
eventOut.mEventType = p.readInt();
eventOut.mTimeStamp = p.readLong();
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 605deac..51397a2 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -22,6 +22,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.PendingIntent;
@@ -234,6 +235,29 @@
@SystemApi
public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED";
+
+ /**
+ * App usage observers will consider the task root package the source of usage.
+ * @hide
+ */
+ @SystemApi
+ public static final int USAGE_SOURCE_TASK_ROOT_ACTIVITY = 1;
+
+ /**
+ * App usage observers will consider the visible activity's package the source of usage.
+ * @hide
+ */
+ @SystemApi
+ public static final int USAGE_SOURCE_CURRENT_ACTIVITY = 2;
+
+ /** @hide */
+ @IntDef(prefix = { "USAGE_SOURCE_" }, value = {
+ USAGE_SOURCE_TASK_ROOT_ACTIVITY,
+ USAGE_SOURCE_CURRENT_ACTIVITY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UsageSource {}
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private static final UsageEvents sEmptyResults = new UsageEvents();
@@ -595,7 +619,7 @@
* @param timeLimit The total time the set of apps can be in the foreground before the
* callbackIntent is delivered. Must be at least one minute.
* @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
- * @param callbackIntent The PendingIntent that will be dispatched when the time limit is
+ * @param callbackIntent The PendingIntent that will be dispatched when the usage limit is
* exceeded by the group of apps. The delivered Intent will also contain
* the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
* {@link #EXTRA_TIME_USED}. Cannot be null.
@@ -658,14 +682,14 @@
* @param sessionThresholdTimeUnit The unit for time specified in {@code sessionThreshold}.
* Cannot be null.
* @param limitReachedCallbackIntent The {@link PendingIntent} that will be dispatched when the
- * time limit is exceeded by the group of apps. The delivered
- * Intent will also contain the extras {@link
+ * usage limit is exceeded by the group of apps. The
+ * delivered Intent will also contain the extras {@link
* #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and {@link
* #EXTRA_TIME_USED}. Cannot be null.
* @param sessionEndCallbackIntent The {@link PendingIntent} that will be dispatched when the
- * session has ended after the time limit has been exceeded. The
- * session is considered at its end after the {@code observed}
- * usage has stopped and an additional {@code
+ * session has ended after the usage limit has been exceeded.
+ * The session is considered at its end after the {@code
+ * observed} usage has stopped and an additional {@code
* sessionThresholdTime} has passed. The delivered Intent will
* also contain the extras {@link #EXTRA_OBSERVER_ID} and {@link
* #EXTRA_TIME_USED}. Can be null.
@@ -712,6 +736,74 @@
}
/**
+ * Register a usage limit observer that receives a callback on the provided intent when the
+ * sum of usages of apps and tokens in the provided {@code observedEntities} array exceeds the
+ * {@code timeLimit} specified. The structure of a token is a {@link String} with the reporting
+ * package's name and a token that the calling app will use, separated by the forward slash
+ * character. Example: com.reporting.package/5OM3*0P4QU3-7OK3N
+ * <p>
+ * Registering an {@code observerId} that was already registered will override the previous one.
+ * No more than 1000 unique {@code observerId} may be registered by a single uid
+ * at any one time.
+ * A limit may be unregistered via {@link #unregisterAppUsageLimitObserver}
+ * <p>
+ * This method is similar to {@link #registerAppUsageObserver}, but the usage limit set here
+ * will be visible to the launcher so that it can report the limit to the user and how much
+ * of it is remaining.
+ * @see android.content.pm.LauncherApps#getAppUsageLimit
+ *
+ * @param observerId A unique id associated with the group of apps to be monitored. There can
+ * be multiple groups with common packages and different time limits.
+ * @param observedEntities The list of packages and token to observe for usage time. Cannot be
+ * null and must include at least one package or token.
+ * @param timeLimit The total time the set of apps can be in the foreground before the
+ * callbackIntent is delivered. Must be at least one minute.
+ * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
+ * @param callbackIntent The PendingIntent that will be dispatched when the usage limit is
+ * exceeded by the group of apps. The delivered Intent will also contain
+ * the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
+ * {@link #EXTRA_TIME_USED}. Cannot be null.
+ * @throws SecurityException if the caller doesn't have both SUSPEND_APPS and OBSERVE_APP_USAGE
+ * permissions.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.SUSPEND_APPS,
+ android.Manifest.permission.OBSERVE_APP_USAGE})
+ public void registerAppUsageLimitObserver(int observerId, @NonNull String[] observedEntities,
+ long timeLimit, @NonNull TimeUnit timeUnit, @NonNull PendingIntent callbackIntent) {
+ try {
+ mService.registerAppUsageLimitObserver(observerId, observedEntities,
+ timeUnit.toMillis(timeLimit), callbackIntent, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregister the app usage limit observer specified by the {@code observerId}.
+ * This will only apply to any observer registered by this application. Unregistering
+ * an observer that was already unregistered or never registered will have no effect.
+ *
+ * @param observerId The id of the observer that was previously registered.
+ * @throws SecurityException if the caller doesn't have both SUSPEND_APPS and OBSERVE_APP_USAGE
+ * permissions.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.SUSPEND_APPS,
+ android.Manifest.permission.OBSERVE_APP_USAGE})
+ public void unregisterAppUsageLimitObserver(int observerId) {
+ try {
+ mService.unregisterAppUsageLimitObserver(observerId, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Report usage associated with a particular {@code token} has started. Tokens are app defined
* strings used to represent usage of in-app features. Apps with the {@link
* android.Manifest.permission#OBSERVE_APP_USAGE} permission can register time limit observers
@@ -719,6 +811,7 @@
* and usage will be considered stopped if the activity stops or crashes.
* @see #registerAppUsageObserver
* @see #registerUsageSessionObserver
+ * @see #registerAppUsageLimitObserver
*
* @param activity The activity {@code token} is associated with.
* @param token The token to report usage against.
@@ -742,6 +835,7 @@
* {@code activity} and usage will be considered stopped if the activity stops or crashes.
* @see #registerAppUsageObserver
* @see #registerUsageSessionObserver
+ * @see #registerAppUsageLimitObserver
*
* @param activity The activity {@code token} is associated with.
* @param token The token to report usage against.
@@ -776,6 +870,38 @@
}
}
+ /**
+ * Get what App Usage Observers will consider the source of usage for an activity. Usage Source
+ * is decided at boot and will not change until next boot.
+ * @see #USAGE_SOURCE_TASK_ROOT_ACTIVITY
+ * @see #USAGE_SOURCE_CURRENT_ACTIVITY
+ *
+ * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and
+ * is not the profile owner of this user.
+ * @hide
+ */
+ @SystemApi
+ public @UsageSource int getUsageSource() {
+ try {
+ return mService.getUsageSource();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Force the Usage Source be reread from global settings.
+ * @hide
+ */
+ @TestApi
+ public void forceUsageSourceSettingRead() {
+ try {
+ mService.forceUsageSourceSettingRead();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** @hide */
public static String reasonToString(int standbyReason) {
StringBuilder sb = new StringBuilder();
@@ -845,6 +971,22 @@
return sb.toString();
}
+ /** @hide */
+ public static String usageSourceToString(int usageSource) {
+ switch (usageSource) {
+ case USAGE_SOURCE_TASK_ROOT_ACTIVITY:
+ return "TASK_ROOT_ACTIVITY";
+ case USAGE_SOURCE_CURRENT_ACTIVITY:
+ return "CURRENT_ACTIVITY";
+ default:
+ StringBuilder sb = new StringBuilder();
+ sb.append("UNKNOWN(");
+ sb.append(usageSource);
+ sb.append(")");
+ return sb.toString();
+ }
+ }
+
/**
* {@hide}
* Temporarily whitelist the specified app for a short duration. This is to allow an app
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index cc3ab00..3d3c03a 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -20,6 +20,7 @@
import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.content.ComponentName;
import android.content.res.Configuration;
+import android.os.UserHandle;
import java.util.List;
import java.util.Set;
@@ -40,9 +41,11 @@
* {@link UsageEvents}
* @param instanceId For activity, hashCode of ActivityRecord's appToken.
* For non-activity, it is not used.
+ * @param taskRoot For activity, the name of the package at the root of the task
+ * For non-activity, it is not used.
*/
public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType,
- int instanceId);
+ int instanceId, ComponentName taskRoot);
/**
* Reports an event to the UsageStatsManager.
@@ -268,4 +271,40 @@
* @param userId which user the app is associated with
*/
public abstract void reportExemptedSyncStart(String packageName, @UserIdInt int userId);
+
+ /**
+ * Returns an object describing the app usage limit for the given package which was set via
+ * {@link UsageStatsManager#registerAppUsageLimitObserver}.
+ * If there are multiple limits that apply to the package, the one with the smallest
+ * time remaining will be returned.
+ *
+ * @param packageName name of the package whose app usage limit will be returned
+ * @param user the user associated with the limit
+ * @return an {@link AppUsageLimitData} object describing the app time limit containing
+ * the given package, with the smallest time remaining.
+ */
+ public abstract AppUsageLimitData getAppUsageLimit(String packageName, UserHandle user);
+
+ /** A class which is used to share the usage limit data for an app or a group of apps. */
+ public static class AppUsageLimitData {
+ private final boolean mGroupLimit;
+ private final long mTotalUsageLimit;
+ private final long mUsageRemaining;
+
+ public AppUsageLimitData(boolean groupLimit, long totalUsageLimit, long usageRemaining) {
+ this.mGroupLimit = groupLimit;
+ this.mTotalUsageLimit = totalUsageLimit;
+ this.mUsageRemaining = usageRemaining;
+ }
+
+ public boolean isGroupLimit() {
+ return mGroupLimit;
+ }
+ public long getTotalUsageLimit() {
+ return mTotalUsageLimit;
+ }
+ public long getUsageRemaining() {
+ return mUsageRemaining;
+ }
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 1945b2f..ab8c196 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -36,6 +36,7 @@
import android.content.Context;
import android.os.BatteryStats;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
@@ -648,6 +649,32 @@
private final Object mLock = new Object();
private final Map<LeScanCallback, ScanCallback> mLeScanClients;
+ private static final Map<BluetoothDevice, List<Pair<MetadataListener, Handler>>>
+ sMetadataListeners = new HashMap<>();
+
+ /**
+ * Bluetooth metadata listener. Overrides the default BluetoothMetadataListener
+ * implementation.
+ */
+ private static final IBluetoothMetadataListener sBluetoothMetadataListener =
+ new IBluetoothMetadataListener.Stub() {
+ @Override
+ public void onMetadataChanged(BluetoothDevice device, int key, String value) {
+ synchronized (sMetadataListeners) {
+ if (sMetadataListeners.containsKey(device)) {
+ List<Pair<MetadataListener, Handler>> list = sMetadataListeners.get(device);
+ for (Pair<MetadataListener, Handler> pair : list) {
+ MetadataListener listener = pair.first;
+ Handler handler = pair.second;
+ handler.post(() -> {
+ listener.onMetadataChanged(device, key, value);
+ });
+ }
+ }
+ }
+ return;
+ }
+ };
/**
* Get a handle to the default local Bluetooth adapter.
@@ -1873,6 +1900,20 @@
}
/**
+ * Return true if Hearing Aid Profile is supported.
+ *
+ * @return true if phone supports Hearing Aid Profile
+ */
+ private boolean isHearingAidProfileSupported() {
+ try {
+ return mManagerService.isHearingAidProfileSupported();
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote expection when calling isHearingAidProfileSupported", e);
+ return false;
+ }
+ }
+
+ /**
* Get the maximum number of connected audio devices.
*
* @return the maximum number of connected audio devices
@@ -2024,6 +2065,11 @@
supportedProfiles.add(i);
}
}
+ } else {
+ // Bluetooth is disabled. Just fill in known supported Profiles
+ if (isHearingAidProfileSupported()) {
+ supportedProfiles.add(BluetoothProfile.HEARING_AID);
+ }
}
}
} catch (RemoteException e) {
@@ -2441,15 +2487,16 @@
* Get the profile proxy object associated with the profile.
*
* <p>Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP},
- * {@link BluetoothProfile#GATT}, or {@link BluetoothProfile#GATT_SERVER}. Clients must
- * implement {@link BluetoothProfile.ServiceListener} to get notified of the connection status
- * and to get the proxy object.
+ * {@link BluetoothProfile#GATT}, {@link BluetoothProfile#HEARING_AID}, or {@link
+ * BluetoothProfile#GATT_SERVER}. Clients must implement {@link
+ * BluetoothProfile.ServiceListener} to get notified of the connection status and to get the
+ * proxy object.
*
* @param context Context of the application
* @param listener The service Listener for connection callbacks.
* @param profile The Bluetooth profile; either {@link BluetoothProfile#HEADSET},
- * {@link BluetoothProfile#A2DP}. {@link BluetoothProfile#GATT} or
- * {@link BluetoothProfile#GATT_SERVER}.
+ * {@link BluetoothProfile#A2DP}, {@link BluetoothProfile#GATT}, {@link
+ * BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#GATT_SERVER}.
* @return true on success, false on error
*/
public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,
@@ -2498,8 +2545,11 @@
BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener);
return true;
} else if (profile == BluetoothProfile.HEARING_AID) {
- BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener);
- return true;
+ if (isHearingAidProfileSupported()) {
+ BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener);
+ return true;
+ }
+ return false;
} else {
return false;
}
@@ -2607,6 +2657,16 @@
}
}
}
+ synchronized (sMetadataListeners) {
+ sMetadataListeners.forEach((device, pair) -> {
+ try {
+ mService.registerMetadataListener(sBluetoothMetadataListener,
+ device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register metadata listener", e);
+ }
+ });
+ }
}
public void onBluetoothServiceDown() {
@@ -3090,4 +3150,142 @@
+ "listenUsingInsecureL2capChannel");
return listenUsingInsecureL2capChannel();
}
+
+ /**
+ * Register a {@link #MetadataListener} to receive update about metadata
+ * changes for this {@link BluetoothDevice}.
+ * Registration must be done when Bluetooth is ON and will last until
+ * {@link #unregisterMetadataListener(BluetoothDevice)} is called, even when Bluetooth
+ * restarted in the middle.
+ * All input parameters should not be null or {@link NullPointerException} will be triggered.
+ * The same {@link BluetoothDevice} and {@link #MetadataListener} pair can only be registered
+ * once, double registration would cause {@link IllegalArgumentException}.
+ *
+ * @param device {@link BluetoothDevice} that will be registered
+ * @param listener {@link #MetadataListener} that will receive asynchronous callbacks
+ * @param handler the handler for listener callback
+ * @return true on success, false on error
+ * @throws NullPointerException If one of {@code listener}, {@code device} or {@code handler}
+ * is null.
+ * @throws IllegalArgumentException The same {@link #MetadataListener} and
+ * {@link BluetoothDevice} are registered twice.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean registerMetadataListener(BluetoothDevice device, MetadataListener listener,
+ Handler handler) {
+ if (DBG) Log.d(TAG, "registerMetdataListener()");
+
+ final IBluetooth service = mService;
+ if (service == null) {
+ Log.e(TAG, "Bluetooth is not enabled. Cannot register metadata listener");
+ return false;
+ }
+ if (listener == null) {
+ throw new NullPointerException("listener is null");
+ }
+ if (device == null) {
+ throw new NullPointerException("device is null");
+ }
+ if (handler == null) {
+ throw new NullPointerException("handler is null");
+ }
+
+ synchronized (sMetadataListeners) {
+ List<Pair<MetadataListener, Handler>> listenerList = sMetadataListeners.get(device);
+ if (listenerList == null) {
+ // Create new listener/handler list for registeration
+ listenerList = new ArrayList<>();
+ sMetadataListeners.put(device, listenerList);
+ } else {
+ // Check whether this device was already registed by the lisenter
+ if (listenerList.stream().anyMatch((pair) -> (pair.first.equals(listener)))) {
+ throw new IllegalArgumentException("listener was already regestered"
+ + " for the device");
+ }
+ }
+
+ Pair<MetadataListener, Handler> listenerPair = new Pair(listener, handler);
+ listenerList.add(listenerPair);
+
+ boolean ret = false;
+ try {
+ ret = service.registerMetadataListener(sBluetoothMetadataListener, device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "registerMetadataListener fail", e);
+ } finally {
+ if (!ret) {
+ // Remove listener registered earlier when fail.
+ listenerList.remove(listenerPair);
+ if (listenerList.isEmpty()) {
+ // Remove the device if its listener list is empty
+ sMetadataListeners.remove(device);
+ }
+ }
+ }
+ return ret;
+ }
+ }
+
+ /**
+ * Unregister all {@link MetadataListener} from this {@link BluetoothDevice}.
+ * Unregistration can be done when Bluetooth is either ON or OFF.
+ * {@link #registerMetadataListener(MetadataListener, BluetoothDevice, Handler)} must
+ * be called before unregisteration.
+ * Unregistering a device that is not regestered would cause {@link IllegalArgumentException}.
+ *
+ * @param device {@link BluetoothDevice} that will be unregistered. it
+ * should not be null or {@link NullPointerException} will be triggered.
+ * @return true on success, false on error
+ * @throws NullPointerException If {@code device} is null.
+ * @throws IllegalArgumentException If {@code device} has not been registered before.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean unregisterMetadataListener(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "unregisterMetdataListener()");
+ if (device == null) {
+ throw new NullPointerException("device is null");
+ }
+
+ synchronized (sMetadataListeners) {
+ if (sMetadataListeners.containsKey(device)) {
+ sMetadataListeners.remove(device);
+ } else {
+ throw new IllegalArgumentException("device was not registered");
+ }
+
+ final IBluetooth service = mService;
+ if (service == null) {
+ // Bluetooth is OFF, do nothing to Bluetooth service.
+ return true;
+ }
+ try {
+ return service.unregisterMetadataListener(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "unregisterMetadataListener fail", e);
+ return false;
+ }
+ }
+ }
+
+ /**
+ * This abstract class is used to implement {@link BluetoothAdapter} metadata listener.
+ * @hide
+ */
+ @SystemApi
+ public abstract static class MetadataListener {
+ /**
+ * Callback triggered if the metadata of {@link BluetoothDevice} registered in
+ * {@link #registerMetadataListener}.
+ *
+ * @param device changed {@link BluetoothDevice}.
+ * @param key changed metadata key, one of BluetoothDevice.METADATA_*.
+ * @param value the new value of metadata.
+ */
+ public void onMetadataChanged(BluetoothDevice device, int key, String value) {
+ }
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 235dc5c..17cf702 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -341,6 +341,137 @@
"android.bluetooth.device.action.SDP_RECORD";
/**
+ * Maximum length of a metadata entry, this is to avoid exploding Bluetooth
+ * disk usage
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_MAX_LENGTH = 2048;
+
+ /**
+ * Manufacturer name of this Bluetooth device
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_MANUFACTURER_NAME = 0;
+
+ /**
+ * Model name of this Bluetooth device
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_MODEL_NAME = 1;
+
+ /**
+ * Software version of this Bluetooth device
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_SOFTWARE_VERSION = 2;
+
+ /**
+ * Hardware version of this Bluetooth device
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_HARDWARE_VERSION = 3;
+
+ /**
+ * Package name of the companion app, if any
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_COMPANION_APP = 4;
+
+ /**
+ * URI to the main icon shown on the settings UI
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_MAIN_ICON = 5;
+
+ /**
+ * Whether this device is an untethered headset with left, right and case
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_IS_UNTHETHERED_HEADSET = 6;
+
+ /**
+ * URI to icon of the left headset
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_LEFT_ICON = 7;
+
+ /**
+ * URI to icon of the right headset
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8;
+
+ /**
+ * URI to icon of the headset charging case
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_CASE_ICON = 9;
+
+ /**
+ * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+ * is invalid, of the left headset
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10;
+
+ /**
+ * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+ * is invalid, of the right headset
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11;
+
+ /**
+ * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+ * is invalid, of the headset charging case
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12;
+
+ /**
+ * Whether the left headset is charging
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13;
+
+ /**
+ * Whether the right headset is charging
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14;
+
+ /**
+ * Whether the headset charging case is charging
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15;
+
+ /**
+ * URI to the enhanced settings UI slice, null or empty String means
+ * the UI does not exist
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16;
+
+ /**
* Broadcast Action: This intent is used to broadcast the {@link UUID}
* wrapped as a {@link android.os.ParcelUuid} of the remote device after it
* has been fetched. This intent is sent only when the UUIDs of the remote
@@ -2026,4 +2157,61 @@
Log.e(TAG, "createL2capCocSocket: PLEASE USE THE OFFICIAL API, createInsecureL2capChannel");
return createInsecureL2capChannel(psm);
}
+
+ /**
+ * Set a keyed metadata of this {@link BluetoothDevice} to a
+ * {@link String} value.
+ * Only bonded devices's metadata will be persisted across Bluetooth
+ * restart.
+ * Metadata will be removed when the device's bond state is moved to
+ * {@link #BOND_NONE}.
+ *
+ * @param key must be within the list of BluetoothDevice.METADATA_*
+ * @param value the string data to set for key. Must be less than
+ * {@link BluetoothAdapter#METADATA_MAX_LENGTH} characters in length
+ * @return true on success, false on error
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean setMetadata(int key, String value) {
+ final IBluetooth service = sService;
+ if (service == null) {
+ Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata");
+ return false;
+ }
+ if (value.length() > METADATA_MAX_LENGTH) {
+ throw new IllegalArgumentException("value length is " + value.length()
+ + ", should not over " + METADATA_MAX_LENGTH);
+ }
+ try {
+ return service.setMetadata(this, key, value);
+ } catch (RemoteException e) {
+ Log.e(TAG, "setMetadata fail", e);
+ return false;
+ }
+ }
+
+ /**
+ * Get a keyed metadata for this {@link BluetoothDevice} as {@link String}
+ *
+ * @param key must be within the list of BluetoothDevice.METADATA_*
+ * @return Metadata of the key as string, null on error or not found
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public String getMetadata(int key) {
+ final IBluetooth service = sService;
+ if (service == null) {
+ Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata");
+ return null;
+ }
+ try {
+ return service.getMetadata(this, key);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getMetadata fail", e);
+ return null;
+ }
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java
index 6ed7942..82cc1bc 100644
--- a/core/java/android/bluetooth/BluetoothHearingAid.java
+++ b/core/java/android/bluetooth/BluetoothHearingAid.java
@@ -39,15 +39,14 @@
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
- * This class provides the public APIs to control the Bluetooth Hearing Aid
- * profile.
+ * This class provides the public APIs to control the Hearing Aid profile.
*
* <p>BluetoothHearingAid is a proxy object for controlling the Bluetooth Hearing Aid
* Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
* the BluetoothHearingAid proxy object.
*
- * <p> Each method is protected with its appropriate permission.
- * @hide
+ * <p> Android only supports one set of connected Bluetooth Hearing Aid device at a time. Each
+ * method is protected with its appropriate permission.
*/
public final class BluetoothHearingAid implements BluetoothProfile {
private static final String TAG = "BluetoothHearingAid";
@@ -56,7 +55,8 @@
/**
* Intent used to broadcast the change in connection state of the Hearing Aid
- * profile.
+ * profile. Please note that in the binaural case, there will be two different LE devices for
+ * the left and right side and each device will have their own connection state changes.S
*
* <p>This intent will have 3 extras:
* <ul>
@@ -77,27 +77,6 @@
"android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
/**
- * Intent used to broadcast the change in the Playing state of the Hearing Aid
- * profile.
- *
- * <p>This intent will have 3 extras:
- * <ul>
- * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
- * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
- * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
- * </ul>
- *
- * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
- * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_PLAYING_STATE_CHANGED =
- "android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED";
-
- /**
* Intent used to broadcast the selection of a connected device as active.
*
* <p>This intent will have one extra:
@@ -108,6 +87,8 @@
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
* receive.
+ *
+ * @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@UnsupportedAppUsage
@@ -115,32 +96,38 @@
"android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED";
/**
- * Hearing Aid device is streaming music. This state can be one of
- * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
- * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
+ * This device represents Left Hearing Aid.
+ *
+ * @hide
*/
- public static final int STATE_PLAYING = 10;
-
- /**
- * Hearing Aid device is NOT streaming music. This state can be one of
- * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
- * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
- */
- public static final int STATE_NOT_PLAYING = 11;
-
- /** This device represents Left Hearing Aid. */
public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT;
- /** This device represents Right Hearing Aid. */
+ /**
+ * This device represents Right Hearing Aid.
+ *
+ * @hide
+ */
public static final int SIDE_RIGHT = IBluetoothHearingAid.SIDE_RIGHT;
- /** This device is Monaural. */
+ /**
+ * This device is Monaural.
+ *
+ * @hide
+ */
public static final int MODE_MONAURAL = IBluetoothHearingAid.MODE_MONAURAL;
- /** This device is Binaural (should receive only left or right audio). */
+ /**
+ * This device is Binaural (should receive only left or right audio).
+ *
+ * @hide
+ */
public static final int MODE_BINAURAL = IBluetoothHearingAid.MODE_BINAURAL;
- /** Can't read ClientID for this device */
+ /**
+ * Indicates the HiSyncID could not be read and is unavailable.
+ *
+ * @hide
+ */
public static final long HI_SYNC_ID_INVALID = IBluetoothHearingAid.HI_SYNC_ID_INVALID;
private Context mContext;
@@ -236,12 +223,6 @@
}
}
- @Override
- public void finalize() {
- // The empty finalize needs to be kept or the
- // cts signature tests would fail.
- }
-
/**
* Initiate connection to a profile of the remote bluetooth device.
*
@@ -538,10 +519,6 @@
return "connected";
case STATE_DISCONNECTING:
return "disconnecting";
- case STATE_PLAYING:
- return "playing";
- case STATE_NOT_PLAYING:
- return "not playing";
default:
return "<unknown state " + state + ">";
}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 3c87c73..b8670db 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -185,7 +185,6 @@
/**
* Hearing Aid Device
*
- * @hide
*/
int HEARING_AID = 21;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index cefc700..280f1ac 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -771,7 +771,9 @@
* <p>
* This is not generally intended for third party application developers.
*/
- public abstract String getOpPackageName();
+ public String getOpPackageName() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
/** Return the full application info for this context's package. */
public abstract ApplicationInfo getApplicationInfo();
@@ -2980,9 +2982,11 @@
*
* @see #bindService
*/
- public abstract boolean bindIsolatedService(@RequiresPermission Intent service,
+ public boolean bindIsolatedService(@RequiresPermission Intent service,
@NonNull ServiceConnection conn, @BindServiceFlags int flags,
- @NonNull String instanceName);
+ @NonNull String instanceName) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
/**
* Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle
@@ -3037,8 +3041,10 @@
* a related groups -- higher importance values will be killed before
* lower ones.
*/
- public abstract void updateServiceGroup(@NonNull ServiceConnection conn, int group,
- int importance);
+ public void updateServiceGroup(@NonNull ServiceConnection conn, int group,
+ int importance) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
/**
* Disconnect from an application service. You will no longer receive
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 8497656..b46203c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2008,6 +2008,15 @@
"android.intent.extra.PERMISSION_GROUP_NAME";
/**
+ * Intent extra: The number of milliseconds.
+ * <p>
+ * Type: long
+ * </p>
+ */
+ public static final String EXTRA_DURATION_MILLIS =
+ "android.intent.extra.DURATION_MILLIS";
+
+ /**
* Activity action: Launch UI to review app uses of permissions.
* <p>
* Input: {@link #EXTRA_PERMISSION_NAME} specifies the permission name
@@ -2020,11 +2029,16 @@
* {@link #EXTRA_PERMISSION_NAME}.
* </p>
* <p>
+ * Input: {@link #EXTRA_DURATION_MILLIS} specifies the minimum number of milliseconds of recent
+ * activity to show (optional). Must be non-negative.
+ * </p>
+ * <p>
* Output: Nothing.
* </p>
*
* @see #EXTRA_PERMISSION_NAME
* @see #EXTRA_PERMISSION_GROUP_NAME
+ * @see #EXTRA_DURATION_MILLIS
*
* @hide
*/
@@ -2363,7 +2377,6 @@
/**
* Broadcast Action: An existing version of an application package has been
* rolled back to a previous version.
- * The data contains the name of the package.
*
* <p class="note">This is a protected intent that can only be sent
* by the system.
@@ -3192,7 +3205,18 @@
*
* <p class="note">This is a protected intent that can only be sent
* by the system.
+ *
+ * <p class="note">If the user has chosen a {@link android.telecom.CallRedirectionService} to
+ * handle redirection of outgoing calls, this intent will NOT be sent as an ordered broadcast.
+ * This means that attempts to re-write the outgoing call by other apps using this intent will
+ * be ignored.
+ * </p>
+ *
+ * @deprecated Apps that redirect outgoing calls should use the
+ * {@link android.telecom.CallRedirectionService} API. Apps that perform call screening
+ * should use the {@link android.telecom.CallScreeningService} API.
*/
+ @Deprecated
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_NEW_OUTGOING_CALL =
"android.intent.action.NEW_OUTGOING_CALL";
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 7a2220bf..8e72fa5 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -54,6 +54,7 @@
this(context, IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE)));
}
+
/**
* Request that an overlay package is enabled and any other overlay packages with the same
* target package and category are disabled.
@@ -75,6 +76,26 @@
}
/**
+ * Request that an overlay package is enabled.
+ *
+ * @param packageName the name of the overlay package to enable.
+ * @param enable {@code false} if the overlay should be turned off.
+ * @param userId The user for which to change the overlay.
+ * @return true if the system successfully registered the request, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean setEnabled(@Nullable final String packageName, final boolean enable,
+ int userId) {
+ try {
+ return mService.setEnabled(packageName, enable, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns information about all overlays for the given target package for
* the specified user. The returned list is ordered according to the
* overlay priority with the highest priority at the end of the list.
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 3cfbe0c..47034a6 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -508,7 +508,7 @@
/**
* Bit in {@link #privateFlags} indicating if the activity should be shown when locked in case
* an activity behind this can also be shown when locked.
- * See android.R.attr#inheritShowWhenLocked
+ * See {@link android.R.attr#inheritShowWhenLocked}.
* @hide
*/
public static final int FLAG_INHERIT_SHOW_WHEN_LOCKED = 0x1;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 576466f..5d6d144 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1955,6 +1955,11 @@
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES) != 0;
}
+ /** @hide */
+ public boolean isCodeIntegrityPreferred() {
+ return (privateFlags & PRIVATE_FLAG_PREFER_CODE_INTEGRITY) != 0;
+ }
+
/**
* Returns whether or not this application was installed as a virtual preload.
*/
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index db2b6fd..d1bc377 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -23,6 +23,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IOnAppsChangedListener;
+import android.content.pm.LauncherApps;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
@@ -56,6 +57,9 @@
ApplicationInfo getApplicationInfo(
String callingPackage, String packageName, int flags, in UserHandle user);
+ LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage, String packageName,
+ in UserHandle user);
+
ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName,
in List shortcutIds, in ComponentName componentName, int flags, in UserHandle user);
void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/core/java/android/content/pm/LauncherApps.aidl
similarity index 66%
copy from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
copy to core/java/android/content/pm/LauncherApps.aidl
index 62a8c48..1d98ad1 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/core/java/android/content/pm/LauncherApps.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2013 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-package com.android.testing.alarmservice;
+package android.content.pm;
-interface Alarm {
- int prepare();
- int setAlarmAndWait(long timeoutMills);
- int done();
-}
\ No newline at end of file
+parcelable LauncherApps.AppUsageLimit;
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 766c566..89630e1 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -758,6 +758,27 @@
}
/**
+ * Returns an object describing the app usage limit for the given package.
+ * If there are multiple limits that apply to the package, the one with the smallest
+ * time remaining will be returned.
+ *
+ * @param packageName name of the package whose app usage limit will be returned
+ * @param user the user of the package
+ *
+ * @return an {@link AppUsageLimit} object describing the app time limit containing
+ * the given package with the smallest time remaining, or {@code null} if none exist.
+ * @throws SecurityException when the caller is not the active launcher.
+ */
+ @Nullable
+ public LauncherApps.AppUsageLimit getAppUsageLimit(String packageName, UserHandle user) {
+ try {
+ return mService.getAppUsageLimit(mContext.getPackageName(), packageName, user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks if the activity exists and it enabled for a profile.
*
* @param component The activity to check.
@@ -1632,4 +1653,86 @@
return 0;
}
}
+
+ /**
+ * A class that encapsulates information about the usage limit set for an app or
+ * a group of apps.
+ *
+ * <p>The launcher can query specifics about the usage limit such as if it is a group limit,
+ * how much usage time the limit has, and how much of the total usage time is remaining
+ * via the APIs available in this class.
+ *
+ * @see #getAppUsageLimit(String, UserHandle)
+ */
+ public static final class AppUsageLimit implements Parcelable {
+ private final boolean mGroupLimit;
+ private final long mTotalUsageLimit;
+ private final long mUsageRemaining;
+
+ /** @hide */
+ public AppUsageLimit(boolean groupLimit, long totalUsageLimit, long usageRemaining) {
+ this.mGroupLimit = groupLimit;
+ this.mTotalUsageLimit = totalUsageLimit;
+ this.mUsageRemaining = usageRemaining;
+ }
+
+ /**
+ * Returns whether this limit refers to a group of apps.
+ *
+ * @return {@code TRUE} if the limit refers to a group of apps, {@code FALSE} otherwise.
+ * @hide
+ */
+ public boolean isGroupLimit() {
+ return mGroupLimit;
+ }
+
+ /**
+ * Returns the total usage limit in milliseconds set for an app or a group of apps.
+ *
+ * @return the total usage limit in milliseconds
+ */
+ public long getTotalUsageLimit() {
+ return mTotalUsageLimit;
+ }
+
+ /**
+ * Returns the usage remaining in milliseconds for an app or the group of apps
+ * this limit refers to.
+ *
+ * @return the usage remaining in milliseconds
+ */
+ public long getUsageRemaining() {
+ return mUsageRemaining;
+ }
+
+ private AppUsageLimit(Parcel source) {
+ mGroupLimit = source.readBoolean();
+ mTotalUsageLimit = source.readLong();
+ mUsageRemaining = source.readLong();
+ }
+
+ public static final Creator<AppUsageLimit> CREATOR = new Creator<AppUsageLimit>() {
+ @Override
+ public AppUsageLimit createFromParcel(Parcel source) {
+ return new AppUsageLimit(source);
+ }
+
+ @Override
+ public AppUsageLimit[] newArray(int size) {
+ return new AppUsageLimit[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mGroupLimit);
+ dest.writeLong(mTotalUsageLimit);
+ dest.writeLong(mUsageRemaining);
+ }
+ }
}
diff --git a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
index 81e4105..7790067 100644
--- a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
+++ b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
@@ -25,12 +25,6 @@
* Updates a package to ensure that if it targets < P that the org.apache.http.legacy library is
* included by default.
*
- * <p>This is separated out so that it can be conditionally included at build time depending on
- * whether org.apache.http.legacy is on the bootclasspath or not. In order to include this at
- * build time, and remove org.apache.http.legacy from the bootclasspath pass
- * REMOVE_OAHL_FROM_BCP=true on the build command line, otherwise this class will not be included
- * and the
- *
* @hide
*/
@VisibleForTesting
diff --git a/core/java/android/content/pm/PackageBackwardCompatibility.java b/core/java/android/content/pm/PackageBackwardCompatibility.java
index 03eefed..b19196a 100644
--- a/core/java/android/content/pm/PackageBackwardCompatibility.java
+++ b/core/java/android/content/pm/PackageBackwardCompatibility.java
@@ -45,13 +45,9 @@
static {
final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>();
- // Attempt to load and add the optional updater that will only be available when
- // REMOVE_OAHL_FROM_BCP=true. If that could not be found then add the default updater that
- // will remove any references to org.apache.http.library from the package so that it does
- // not try and load the library when it is on the bootclasspath.
- boolean bootClassPathContainsOAHL = !addOptionalUpdater(packageUpdaters,
- "android.content.pm.OrgApacheHttpLegacyUpdater",
- RemoveUnnecessaryOrgApacheHttpLegacyLibrary::new);
+ // Automatically add the org.apache.http.legacy library to the app classpath if the app
+ // targets < P.
+ packageUpdaters.add(new OrgApacheHttpLegacyUpdater());
packageUpdaters.add(new AndroidHidlUpdater());
@@ -70,7 +66,7 @@
PackageSharedLibraryUpdater[] updaterArray = packageUpdaters
.toArray(new PackageSharedLibraryUpdater[0]);
INSTANCE = new PackageBackwardCompatibility(
- bootClassPathContainsOAHL, bootClassPathContainsATB, updaterArray);
+ bootClassPathContainsATB, updaterArray);
}
/**
@@ -116,15 +112,12 @@
return INSTANCE;
}
- private final boolean mBootClassPathContainsOAHL;
-
private final boolean mBootClassPathContainsATB;
private final PackageSharedLibraryUpdater[] mPackageUpdaters;
- public PackageBackwardCompatibility(boolean bootClassPathContainsOAHL,
+ public PackageBackwardCompatibility(
boolean bootClassPathContainsATB, PackageSharedLibraryUpdater[] packageUpdaters) {
- this.mBootClassPathContainsOAHL = bootClassPathContainsOAHL;
this.mBootClassPathContainsATB = bootClassPathContainsATB;
this.mPackageUpdaters = packageUpdaters;
}
@@ -148,14 +141,6 @@
}
/**
- * True if the org.apache.http.legacy is on the bootclasspath, false otherwise.
- */
- @VisibleForTesting
- public static boolean bootClassPathContainsOAHL() {
- return INSTANCE.mBootClassPathContainsOAHL;
- }
-
- /**
* True if the android.test.base is on the bootclasspath, false otherwise.
*/
@VisibleForTesting
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 94b7c45..73b1f4e 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1543,6 +1543,13 @@
this.isStaged = true;
}
+ /**
+ * Set this session to be installing an APEX package.
+ */
+ public void setInstallAsApex() {
+ installFlags |= PackageManager.INSTALL_APEX;
+ }
+
/** {@hide} */
public void dump(IndentingPrintWriter pw) {
pw.printPair("mode", mode);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b845673..783ee64 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2060,6 +2060,14 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has a secure implementation of keyguard, meaning the
+ * device supports PIN, pattern and password as defined in Android CDD
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device includes an accelerometer.
*/
@SdkConstant(SdkConstantType.FEATURE)
@@ -2215,6 +2223,13 @@
public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * supports attaching to IMS implementations using the ImsService API in telephony.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports connecting to USB devices
* as the USB host.
@@ -5764,7 +5779,7 @@
*/
@RequiresPermission(value = android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE,
conditional = true)
- public abstract void setComponentEnabledSetting(ComponentName componentName,
+ public abstract void setComponentEnabledSetting(@NonNull ComponentName componentName,
@EnabledState int newState, @EnabledFlags int flags);
/**
@@ -5778,7 +5793,7 @@
* @return Returns the current enabled state for the component.
*/
public abstract @EnabledState int getComponentEnabledSetting(
- ComponentName componentName);
+ @NonNull ComponentName componentName);
/**
* Set the enabled setting for an application
@@ -5793,7 +5808,7 @@
*/
@RequiresPermission(value = android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE,
conditional = true)
- public abstract void setApplicationEnabledSetting(String packageName,
+ public abstract void setApplicationEnabledSetting(@NonNull String packageName,
@EnabledState int newState, @EnabledFlags int flags);
/**
@@ -5807,7 +5822,7 @@
* @return Returns the current enabled state for the application.
* @throws IllegalArgumentException if the named package does not exist.
*/
- public abstract @EnabledState int getApplicationEnabledSetting(String packageName);
+ public abstract @EnabledState int getApplicationEnabledSetting(@NonNull String packageName);
/**
* Flush the package restrictions for a given user to disk. This forces the package restrictions
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index c7320b0..c9a4c82 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -794,6 +794,12 @@
"android.content.pm.extra.ENABLE_ROLLBACK_INSTALL_FLAGS";
/**
+ * Extra field name for the set of installed users for a given rollback package.
+ */
+ public static final String EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS =
+ "android.content.pm.extra.ENABLE_ROLLBACK_INSTALLED_USERS";
+
+ /**
* Used as the {@code enableRollbackCode} argument for
* {@link PackageManagerInternal#setEnableRollbackCode} to indicate that
* enabling rollback succeeded.
@@ -827,4 +833,10 @@
* Ask the package manager to compile layouts in the given package.
*/
public abstract boolean compileLayouts(String packageName);
+
+ /*
+ * Inform the package manager that the pending package install identified by
+ * {@code token} can be completed.
+ */
+ public abstract void finishPackageInstall(int token, boolean didLaunch);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 88a240f..eb59cfc 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -8512,7 +8512,9 @@
collectCerts ? PackageParser.PARSE_COLLECT_CERTIFICATES : 0);
pi.packageName = apk.packageName;
+ ai.packageName = apk.packageName;
pi.setLongVersionCode(apk.getLongVersionCode());
+ ai.setVersionCode(apk.getLongVersionCode());
if (collectCerts) {
if (apk.signingDetails.hasPastSigningCertificates()) {
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 6e519c1..a8c3b88 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -475,14 +475,6 @@
final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
for (ResolveInfo resolveInfo : resolveInfos) {
try {
- // if this package is not one of those changedUids, we don't need to scan it,
- // since nothing in it changed, so save a call to parseServiceInfo, which
- // can cause a large amount of the package apk to be loaded into memory.
- // if this is the initial scan, changedUids will be null, and containsUid will
- // trivially return true, and will call parseServiceInfo
- if (!containsUid(changedUids, resolveInfo.serviceInfo.applicationInfo.uid)) {
- continue;
- }
ServiceInfo<V> info = parseServiceInfo(resolveInfo);
if (info == null) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl
index 7f557cd..420bcb6 100644
--- a/core/java/android/content/rollback/IRollbackManager.aidl
+++ b/core/java/android/content/rollback/IRollbackManager.aidl
@@ -33,6 +33,12 @@
void executeRollback(in RollbackInfo rollback, String callerPackageName,
in IntentSender statusReceiver);
+ // Exposed for use from the system server only. Callback from the package
+ // manager during the install flow when user data can be restored for a given
+ // package.
+ void restoreUserData(String packageName, int userId, int appId, long ceDataInode,
+ String seInfo, int token);
+
// Exposed for test purposes only.
void reloadPersistedData();
diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java
index 2040024..4644a83 100644
--- a/core/java/android/content/rollback/PackageRollbackInfo.java
+++ b/core/java/android/content/rollback/PackageRollbackInfo.java
@@ -17,11 +17,10 @@
package android.content.rollback;
import android.annotation.SystemApi;
+import android.content.pm.VersionedPackage;
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.Objects;
-
/**
* Information about a rollback available for a particular package.
*
@@ -29,59 +28,41 @@
*/
@SystemApi
public final class PackageRollbackInfo implements Parcelable {
- /**
- * The name of a package being rolled back.
- */
- public final String packageName;
+
+ private final VersionedPackage mVersionRolledBackFrom;
+ private final VersionedPackage mVersionRolledBackTo;
/**
- * The version the package was rolled back from.
+ * Returns the name of the package to roll back from.
*/
- public final PackageVersion higherVersion;
-
- /**
- * The version the package was rolled back to.
- */
- public final PackageVersion lowerVersion;
-
- /**
- * Represents a version of a package.
- */
- public static class PackageVersion {
- public final long versionCode;
-
- // TODO(b/120200473): Include apk sha or some other way to distinguish
- // between two different apks with the same version code.
- public PackageVersion(long versionCode) {
- this.versionCode = versionCode;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other instanceof PackageVersion) {
- PackageVersion otherVersion = (PackageVersion) other;
- return versionCode == otherVersion.versionCode;
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(versionCode);
- }
+ public String getPackageName() {
+ return mVersionRolledBackFrom.getPackageName();
}
- public PackageRollbackInfo(String packageName,
- PackageVersion higherVersion, PackageVersion lowerVersion) {
- this.packageName = packageName;
- this.higherVersion = higherVersion;
- this.lowerVersion = lowerVersion;
+ /**
+ * Returns the version of the package rolled back from.
+ */
+ public VersionedPackage getVersionRolledBackFrom() {
+ return mVersionRolledBackFrom;
+ }
+
+ /**
+ * Returns the version of the package rolled back to.
+ */
+ public VersionedPackage getVersionRolledBackTo() {
+ return mVersionRolledBackTo;
+ }
+
+ /** @hide */
+ public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
+ VersionedPackage packageRolledBackTo) {
+ this.mVersionRolledBackFrom = packageRolledBackFrom;
+ this.mVersionRolledBackTo = packageRolledBackTo;
}
private PackageRollbackInfo(Parcel in) {
- this.packageName = in.readString();
- this.higherVersion = new PackageVersion(in.readLong());
- this.lowerVersion = new PackageVersion(in.readLong());
+ this.mVersionRolledBackFrom = VersionedPackage.CREATOR.createFromParcel(in);
+ this.mVersionRolledBackTo = VersionedPackage.CREATOR.createFromParcel(in);
}
@Override
@@ -91,9 +72,8 @@
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeString(packageName);
- out.writeLong(higherVersion.versionCode);
- out.writeLong(lowerVersion.versionCode);
+ mVersionRolledBackFrom.writeToParcel(out, flags);
+ mVersionRolledBackTo.writeToParcel(out, flags);
}
public static final Parcelable.Creator<PackageRollbackInfo> CREATOR =
diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java
index 66df4fe..0803a7c 100644
--- a/core/java/android/content/rollback/RollbackInfo.java
+++ b/core/java/android/content/rollback/RollbackInfo.java
@@ -30,6 +30,11 @@
public final class RollbackInfo implements Parcelable {
/**
+ * A unique identifier for the rollback.
+ */
+ private final int mRollbackId;
+
+ /**
* The package that needs to be rolled back.
*/
public final PackageRollbackInfo targetPackage;
@@ -40,12 +45,21 @@
// staged installs is supported.
/** @hide */
- public RollbackInfo(PackageRollbackInfo targetPackage) {
+ public RollbackInfo(int rollbackId, PackageRollbackInfo targetPackage) {
+ this.mRollbackId = rollbackId;
this.targetPackage = targetPackage;
}
private RollbackInfo(Parcel in) {
- this.targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in);
+ mRollbackId = in.readInt();
+ targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in);
+ }
+
+ /**
+ * Returns a unique identifier for this rollback.
+ */
+ public int getRollbackId() {
+ return mRollbackId;
}
@Override
@@ -55,6 +69,7 @@
@Override
public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mRollbackId);
targetPackage.writeToParcel(out, flags);
}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 105ae68..9758308 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3722,12 +3722,12 @@
/**
* <p>String containing the ids of the underlying physical cameras.</p>
- * <p>For a logical camera, this is concatenation of all underlying physical camera ids.
- * The null terminator for physical camera id must be preserved so that the whole string
- * can be tokenized using '\0' to generate list of physical camera ids.</p>
- * <p>For example, if the physical camera ids of the logical camera are "2" and "3", the
+ * <p>For a logical camera, this is concatenation of all underlying physical camera IDs.
+ * The null terminator for physical camera ID must be preserved so that the whole string
+ * can be tokenized using '\0' to generate list of physical camera IDs.</p>
+ * <p>For example, if the physical camera IDs of the logical camera are "2" and "3", the
* value of this tag will be ['2', '\0', '3', '\0'].</p>
- * <p>The number of physical camera ids must be no less than 2.</p>
+ * <p>The number of physical camera IDs must be no less than 2.</p>
* <p><b>Units</b>: UTF-8 null-terminated string</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Limited capability</b> -
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 402472a..6302aa5 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -428,6 +428,10 @@
* <p>If this is supported, {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} will
* additionally return a min frame duration that is greater than
* zero for each supported size-format combination.</p>
+ * <p>For camera devices with LOGICAL_MULTI_CAMERA capability, when the underlying active
+ * physical camera switches, exposureTime, sensitivity, and lens properties may change
+ * even if AE/AF is locked. However, the overall auto exposure and auto focus experience
+ * for users will be consistent. Refer to LOGICAL_MULTI_CAMERA capability for details.</p>
*
* @see CaptureRequest#BLACK_LEVEL_LOCK
* @see CaptureRequest#CONTROL_AE_LOCK
@@ -485,6 +489,10 @@
* will accurately report the values applied by AWB in the result.</p>
* <p>A given camera device may also support additional post-processing
* controls, but this capability only covers the above list of controls.</p>
+ * <p>For camera devices with LOGICAL_MULTI_CAMERA capability, when underlying active
+ * physical camera switches, tonemap, white balance, and shading map may change even if
+ * awb is locked. However, the overall post-processing experience for users will be
+ * consistent. Refer to LOGICAL_MULTI_CAMERA capability for details.</p>
*
* @see CaptureRequest#COLOR_CORRECTION_ABERRATION_MODE
* @see CameraCharacteristics#COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES
@@ -847,7 +855,7 @@
* </li>
* <li>The SENSOR_INFO_TIMESTAMP_SOURCE of the logical device and physical devices must be
* the same.</li>
- * <li>The logical camera device must be LIMITED or higher device.</li>
+ * <li>The logical camera must be LIMITED or higher device.</li>
* </ul>
* <p>Both the logical camera device and its underlying physical devices support the
* mandatory stream combinations required for their device levels.</p>
@@ -867,13 +875,84 @@
* <p>Using physical streams in place of a logical stream of the same size and format will
* not slow down the frame rate of the capture, as long as the minimum frame duration
* of the physical and logical streams are the same.</p>
+ * <p>A logical camera device's dynamic metadata may contain
+ * {@link CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID android.logicalMultiCamera.activePhysicalId} to notify the application of the current
+ * active physical camera Id. An active physical camera is the physical camera from which
+ * the logical camera's main image data outputs (YUV or RAW) and metadata come from.
+ * In addition, this serves as an indication which physical camera is used to output to
+ * a RAW stream, or in case only physical cameras support RAW, which physical RAW stream
+ * the application should request.</p>
+ * <p>Logical camera's static metadata tags below describe the default active physical
+ * camera. An active physical camera is default if it's used when application directly
+ * uses requests built from a template. All templates will default to the same active
+ * physical camera.</p>
+ * <ul>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT android.sensor.info.colorFilterArrangement}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE android.sensor.info.physicalSize}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL android.sensor.info.whiteLevel}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_LENS_SHADING_APPLIED android.sensor.info.lensShadingApplied}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 android.sensor.referenceIlluminant1}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 android.sensor.referenceIlluminant2}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1 android.sensor.calibrationTransform1}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM2 android.sensor.calibrationTransform2}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM1 android.sensor.colorTransform1}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM2 android.sensor.colorTransform2}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_FORWARD_MATRIX1 android.sensor.forwardMatrix1}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_FORWARD_MATRIX2 android.sensor.forwardMatrix2}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN android.sensor.blackLevelPattern}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_MAX_ANALOG_SENSITIVITY android.sensor.maxAnalogSensitivity}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_AVAILABLE_TEST_PATTERN_MODES android.sensor.availableTestPatternModes}</li>
+ * <li>{@link CameraCharacteristics#LENS_INFO_HYPERFOCAL_DISTANCE android.lens.info.hyperfocalDistance}</li>
+ * <li>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance}</li>
+ * <li>{@link CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION android.lens.info.focusDistanceCalibration}</li>
+ * <li>{@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}</li>
+ * <li>{@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}</li>
+ * <li>{@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}</li>
+ * <li>{@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference}</li>
+ * <li>{@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion}</li>
+ * </ul>
+ * <p>To maintain backward compatibility, the capture request and result metadata tags
+ * required for basic camera functionalities will be solely based on the
+ * logical camera capabiltity. Other request and result metadata tags, on the other
+ * hand, will be based on current active physical camera. For example, the physical
+ * cameras' sensor sensitivity and lens capability could be different from each other.
+ * So when the application manually controls sensor exposure time/gain, or does manual
+ * focus control, it must checks the current active physical camera's exposure, gain,
+ * and focus distance range.</p>
*
* @see CameraCharacteristics#LENS_DISTORTION
+ * @see CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION
+ * @see CameraCharacteristics#LENS_INFO_HYPERFOCAL_DISTANCE
+ * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE
* @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION
* @see CameraCharacteristics#LENS_POSE_REFERENCE
* @see CameraCharacteristics#LENS_POSE_ROTATION
* @see CameraCharacteristics#LENS_POSE_TRANSLATION
+ * @see CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID
* @see CameraCharacteristics#LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE
+ * @see CameraCharacteristics#SENSOR_AVAILABLE_TEST_PATTERN_MODES
+ * @see CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN
+ * @see CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1
+ * @see CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM2
+ * @see CameraCharacteristics#SENSOR_COLOR_TRANSFORM1
+ * @see CameraCharacteristics#SENSOR_COLOR_TRANSFORM2
+ * @see CameraCharacteristics#SENSOR_FORWARD_MATRIX1
+ * @see CameraCharacteristics#SENSOR_FORWARD_MATRIX2
+ * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT
+ * @see CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE
+ * @see CameraCharacteristics#SENSOR_INFO_LENS_SHADING_APPLIED
+ * @see CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION
+ * @see CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE
+ * @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL
+ * @see CameraCharacteristics#SENSOR_MAX_ANALOG_SENSITIVITY
+ * @see CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
*/
public static final int REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11;
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 8ebaf2f..5f05cfb 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2146,7 +2146,6 @@
/**
* <p>32 characters describing GPS algorithm to
* include in EXIF.</p>
- * <p><b>Units</b>: UTF-8 null-terminated string</p>
* <p>This key is available on all devices.</p>
* @hide
*/
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 3d70c51..585c597 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2470,7 +2470,6 @@
/**
* <p>32 characters describing GPS algorithm to
* include in EXIF.</p>
- * <p><b>Units</b>: UTF-8 null-terminated string</p>
* <p>This key is available on all devices.</p>
* @hide
*/
@@ -4638,6 +4637,23 @@
new Key<Float>("android.reprocess.effectiveExposureFactor", float.class);
/**
+ * <p>String containing the ID of the underlying active physical camera.</p>
+ * <p>The ID of the active physical camera that's backing the logical camera. All camera
+ * streams and metadata that are not physical camera specific will be originating from this
+ * physical camera. This must be one of valid physical IDs advertised in the physicalIds
+ * static tag.</p>
+ * <p>For a logical camera made up of physical cameras where each camera's lenses have
+ * different characteristics, the camera device may choose to switch between the physical
+ * cameras when application changes FOCAL_LENGTH or SCALER_CROP_REGION.
+ * At the time of lens switch, this result metadata reflects the new active physical camera
+ * ID.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ */
+ @PublicKey
+ public static final Key<String> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID =
+ new Key<String>("android.logicalMultiCamera.activePhysicalId", String.class);
+
+ /**
* <p>Mode of operation for the lens distortion correction block.</p>
* <p>The lens distortion correction block attempts to improve image quality by fixing
* radial, tangential, or other geometric aberrations in the camera device's optics. If
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
index 70a9f08..fa335c8 100644
--- a/core/java/android/hardware/display/ColorDisplayManager.java
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -17,6 +17,7 @@
package android.hardware.display;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -30,6 +31,9 @@
import com.android.internal.R;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Manages the display's color transforms and modes.
*
@@ -39,6 +43,44 @@
@SystemService(Context.COLOR_DISPLAY_SERVICE)
public final class ColorDisplayManager {
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({CAPABILITY_NONE, CAPABILITY_PROTECTED_CONTENT, CAPABILITY_HARDWARE_ACCELERATION_GLOBAL,
+ CAPABILITY_HARDWARE_ACCELERATION_PER_APP})
+ public @interface CapabilityType {}
+
+ /**
+ * The device does not support color transforms.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int CAPABILITY_NONE = 0x0;
+ /**
+ * The device can properly apply transforms over protected content.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int CAPABILITY_PROTECTED_CONTENT = 0x1;
+ /**
+ * The device's hardware can efficiently apply transforms to the entire display.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int CAPABILITY_HARDWARE_ACCELERATION_GLOBAL = 0x2;
+ /**
+ * The device's hardware can efficiently apply transforms to a specific Surface (window) so
+ * that apps can be transformed independently of one another.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int CAPABILITY_HARDWARE_ACCELERATION_PER_APP = 0x4;
+
private final ColorDisplayManagerInternal mManager;
/**
@@ -114,6 +156,17 @@
return context.getResources().getBoolean(R.bool.config_setColorTransformAccelerated);
}
+ /**
+ * Returns the available software and hardware color transform capabilities of this device.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ public @CapabilityType int getTransformCapabilities() {
+ return mManager.getTransformCapabilities();
+ }
+
private static class ColorDisplayManagerInternal {
private static ColorDisplayManagerInternal sInstance;
@@ -162,5 +215,13 @@
throw e.rethrowFromSystemServer();
}
}
+
+ int getTransformCapabilities() {
+ try {
+ return mCdm.getTransformCapabilities();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
}
diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl
index 644f510..53cb8db 100644
--- a/core/java/android/hardware/display/IColorDisplayManager.aidl
+++ b/core/java/android/hardware/display/IColorDisplayManager.aidl
@@ -22,4 +22,6 @@
boolean setSaturationLevel(int saturationLevel);
boolean setAppSaturationLevel(String packageName, int saturationLevel);
+
+ int getTransformCapabilities();
}
\ No newline at end of file
diff --git a/core/java/android/hardware/hdmi/HdmiUtils.java b/core/java/android/hardware/hdmi/HdmiUtils.java
index 3081738..8c94b78 100644
--- a/core/java/android/hardware/hdmi/HdmiUtils.java
+++ b/core/java/android/hardware/hdmi/HdmiUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,14 +16,18 @@
package android.hardware.hdmi;
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
- * Various utilities to handle HDMI CEC messages.
+ * Various utilities related to HDMI CEC.
*
* TODO(b/110094868): unhide for Q
* @hide
*/
-public class HdmiUtils {
-
+public final class HdmiUtils {
/**
* Return value of {@link #getLocalPortFromPhysicalAddress(int, int)}
*/
@@ -78,4 +82,164 @@
}
return port;
}
+
+ /**
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({HDMI_RELATIVE_POSITION_UNKNOWN, HDMI_RELATIVE_POSITION_DIRECTLY_BELOW,
+ HDMI_RELATIVE_POSITION_BELOW, HDMI_RELATIVE_POSITION_SAME,
+ HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE, HDMI_RELATIVE_POSITION_ABOVE,
+ HDMI_RELATIVE_POSITION_SIBLING, HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH})
+ public @interface HdmiAddressRelativePosition {}
+ /**
+ * HDMI relative position is not determined.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_UNKNOWN = 0;
+ /**
+ * HDMI relative position: directly blow the device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_DIRECTLY_BELOW = 1;
+ /**
+ * HDMI relative position: indirectly below the device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_BELOW = 2;
+ /**
+ * HDMI relative position: the same device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_SAME = 3;
+ /**
+ * HDMI relative position: directly above the device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE = 4;
+ /**
+ * HDMI relative position: indirectly above the device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_ABOVE = 5;
+ /**
+ * HDMI relative position: directly below a same device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_SIBLING = 6;
+ /**
+ * HDMI relative position: different branch.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH = 7;
+
+ private static final int NPOS = -1;
+
+ /**
+ * Check if the given physical address is valid.
+ *
+ * @param address physical address
+ * @return {@code true} if the given address is valid
+ */
+ public static boolean isValidPhysicalAddress(int address) {
+ if (address < 0 || address >= 0xFFFF) {
+ return false;
+ }
+ int mask = 0xF000;
+ boolean hasZero = false;
+ for (int i = 0; i < 4; i++) {
+ if ((address & mask) == 0) {
+ hasZero = true;
+ } else if (hasZero) {
+ // only 0s are valid after a 0.
+ // e.g. 0x1012 is not valid.
+ return false;
+ }
+ mask >>= 4;
+ }
+ return true;
+ }
+
+
+ /**
+ * Returns the relative position of two physical addresses.
+ */
+ @HdmiAddressRelativePosition
+ public static int getHdmiAddressRelativePosition(int src, int dest) {
+ if (src == 0xFFFF || dest == 0xFFFF) {
+ // address not assigned
+ return HDMI_RELATIVE_POSITION_UNKNOWN;
+ }
+ try {
+ int firstDiffPos = physicalAddressFirstDifferentDigitPos(src, dest);
+ if (firstDiffPos == NPOS) {
+ return HDMI_RELATIVE_POSITION_SAME;
+ }
+ int mask = (0xF000 >> (firstDiffPos * 4));
+ int nextPos = firstDiffPos + 1;
+ if ((src & mask) == 0) {
+ // src is above dest
+ if (nextPos == 4) {
+ // last digits are different
+ return HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE;
+ }
+ if (((0xF000 >> (nextPos * 4)) & dest) == 0) {
+ // next digit is 0
+ return HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE;
+ }
+ return HDMI_RELATIVE_POSITION_ABOVE;
+ }
+
+ if ((dest & mask) == 0) {
+ // src is below dest
+ if (nextPos == 4) {
+ // last digits are different
+ return HDMI_RELATIVE_POSITION_DIRECTLY_BELOW;
+ }
+ if (((0xF000 >> (nextPos * 4)) & src) == 0) {
+ // next digit is 0
+ return HDMI_RELATIVE_POSITION_DIRECTLY_BELOW;
+ }
+ return HDMI_RELATIVE_POSITION_BELOW;
+ }
+ if (nextPos == 4) {
+ // last digits are different
+ return HDMI_RELATIVE_POSITION_SIBLING;
+ }
+ if (((0xF000 >> (nextPos * 4)) & src) == 0 && ((0xF000 >> (nextPos * 4)) & dest) == 0) {
+ return HDMI_RELATIVE_POSITION_SIBLING;
+ }
+ return HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH;
+ } catch (IllegalArgumentException e) {
+ // invalid address
+ return HDMI_RELATIVE_POSITION_UNKNOWN;
+ }
+ }
+
+ private static int physicalAddressFirstDifferentDigitPos(int address1, int address2)
+ throws IllegalArgumentException {
+ if (!isValidPhysicalAddress(address1)) {
+ throw new IllegalArgumentException(address1 + " is not a valid address.");
+ }
+ if (!isValidPhysicalAddress(address2)) {
+ throw new IllegalArgumentException(address2 + " is not a valid address.");
+ }
+ int mask = 0xF000;
+ for (int i = 0; i < 4; i++) {
+ if ((address1 & mask) != (address2 & mask)) {
+ return i;
+ }
+ mask = mask >> 4;
+ }
+ return NPOS;
+ }
}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/core/java/android/hardware/location/ActivityChangedEvent.aidl
similarity index 69%
copy from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
copy to core/java/android/hardware/location/ActivityChangedEvent.aidl
index 62a8c48..21f2445 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/core/java/android/hardware/location/ActivityChangedEvent.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2014 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.
@@ -11,13 +11,9 @@
* 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.
+ * limitations under the License
*/
-package com.android.testing.alarmservice;
+package android.hardware.location;
-interface Alarm {
- int prepare();
- int setAlarmAndWait(long timeoutMills);
- int done();
-}
\ No newline at end of file
+parcelable ActivityChangedEvent;
\ No newline at end of file
diff --git a/core/java/android/hardware/location/ActivityChangedEvent.java b/core/java/android/hardware/location/ActivityChangedEvent.java
new file mode 100644
index 0000000..16cfe6e
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityChangedEvent.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2014 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.hardware.location;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.InvalidParameterException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A class representing an event for Activity changes.
+ *
+ * @hide
+ */
+public class ActivityChangedEvent implements Parcelable {
+ private final List<ActivityRecognitionEvent> mActivityRecognitionEvents;
+
+ public ActivityChangedEvent(ActivityRecognitionEvent[] activityRecognitionEvents) {
+ if (activityRecognitionEvents == null) {
+ throw new InvalidParameterException(
+ "Parameter 'activityRecognitionEvents' must not be null.");
+ }
+
+ mActivityRecognitionEvents = Arrays.asList(activityRecognitionEvents);
+ }
+
+ @NonNull
+ public Iterable<ActivityRecognitionEvent> getActivityRecognitionEvents() {
+ return mActivityRecognitionEvents;
+ }
+
+ public static final Creator<ActivityChangedEvent> CREATOR =
+ new Creator<ActivityChangedEvent>() {
+ @Override
+ public ActivityChangedEvent createFromParcel(Parcel source) {
+ int activityRecognitionEventsLength = source.readInt();
+ ActivityRecognitionEvent[] activityRecognitionEvents =
+ new ActivityRecognitionEvent[activityRecognitionEventsLength];
+ source.readTypedArray(activityRecognitionEvents, ActivityRecognitionEvent.CREATOR);
+
+ return new ActivityChangedEvent(activityRecognitionEvents);
+ }
+
+ @Override
+ public ActivityChangedEvent[] newArray(int size) {
+ return new ActivityChangedEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ ActivityRecognitionEvent[] activityRecognitionEventArray =
+ mActivityRecognitionEvents.toArray(new ActivityRecognitionEvent[0]);
+ parcel.writeInt(activityRecognitionEventArray.length);
+ parcel.writeTypedArray(activityRecognitionEventArray, flags);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("[ ActivityChangedEvent:");
+
+ for (ActivityRecognitionEvent event : mActivityRecognitionEvents) {
+ builder.append("\n ");
+ builder.append(event.toString());
+ }
+ builder.append("\n]");
+
+ return builder.toString();
+ }
+}
diff --git a/core/java/android/hardware/location/ActivityRecognitionEvent.java b/core/java/android/hardware/location/ActivityRecognitionEvent.java
new file mode 100644
index 0000000..190030a
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityRecognitionEvent.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014 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.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that represents an Activity Recognition Event.
+ *
+ * @hide
+ */
+public class ActivityRecognitionEvent implements Parcelable {
+ private final String mActivity;
+ private final int mEventType;
+ private final long mTimestampNs;
+
+ public ActivityRecognitionEvent(String activity, int eventType, long timestampNs) {
+ mActivity = activity;
+ mEventType = eventType;
+ mTimestampNs = timestampNs;
+ }
+
+ public String getActivity() {
+ return mActivity;
+ }
+
+ public int getEventType() {
+ return mEventType;
+ }
+
+ public long getTimestampNs() {
+ return mTimestampNs;
+ }
+
+ public static final Creator<ActivityRecognitionEvent> CREATOR =
+ new Creator<ActivityRecognitionEvent>() {
+ @Override
+ public ActivityRecognitionEvent createFromParcel(Parcel source) {
+ String activity = source.readString();
+ int eventType = source.readInt();
+ long timestampNs = source.readLong();
+
+ return new ActivityRecognitionEvent(activity, eventType, timestampNs);
+ }
+
+ @Override
+ public ActivityRecognitionEvent[] newArray(int size) {
+ return new ActivityRecognitionEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mActivity);
+ parcel.writeInt(mEventType);
+ parcel.writeLong(mTimestampNs);
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "Activity='%s', EventType=%s, TimestampNs=%s",
+ mActivity,
+ mEventType,
+ mTimestampNs);
+ }
+}
diff --git a/core/java/android/hardware/location/ActivityRecognitionHardware.java b/core/java/android/hardware/location/ActivityRecognitionHardware.java
new file mode 100644
index 0000000..8acd1ff
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityRecognitionHardware.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2014 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.hardware.location;
+
+import android.Manifest;
+import android.content.Context;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * A class that implements an {@link IActivityRecognitionHardware} backed up by the Activity
+ * Recognition HAL.
+ *
+ * @hide
+ */
+public class ActivityRecognitionHardware extends IActivityRecognitionHardware.Stub {
+ private static final String TAG = "ActivityRecognitionHW";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
+ private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
+ + HARDWARE_PERMISSION + "' not granted to access ActivityRecognitionHardware";
+
+ private static final int INVALID_ACTIVITY_TYPE = -1;
+ private static final int NATIVE_SUCCESS_RESULT = 0;
+ private static final int EVENT_TYPE_DISABLED = 0;
+ private static final int EVENT_TYPE_ENABLED = 1;
+
+ /**
+ * Contains the number of supported Event Types.
+ *
+ * NOTE: increment this counter every time a new EVENT_TYPE_ is added to
+ * com.android.location.provider.ActivityRecognitionProvider
+ */
+ private static final int EVENT_TYPE_COUNT = 3;
+
+ private static ActivityRecognitionHardware sSingletonInstance;
+ private static final Object sSingletonInstanceLock = new Object();
+
+ private final Context mContext;
+ private final int mSupportedActivitiesCount;
+ private final String[] mSupportedActivities;
+ private final int[][] mSupportedActivitiesEnabledEvents;
+ private final SinkList mSinks = new SinkList();
+
+ private static class Event {
+ public int activity;
+ public int type;
+ public long timestamp;
+ }
+
+ private ActivityRecognitionHardware(Context context) {
+ nativeInitialize();
+
+ mContext = context;
+ mSupportedActivities = fetchSupportedActivities();
+ mSupportedActivitiesCount = mSupportedActivities.length;
+ mSupportedActivitiesEnabledEvents = new int[mSupportedActivitiesCount][EVENT_TYPE_COUNT];
+ }
+
+ public static ActivityRecognitionHardware getInstance(Context context) {
+ synchronized (sSingletonInstanceLock) {
+ if (sSingletonInstance == null) {
+ sSingletonInstance = new ActivityRecognitionHardware(context);
+ }
+
+ return sSingletonInstance;
+ }
+ }
+
+ public static boolean isSupported() {
+ return nativeIsSupported();
+ }
+
+ @Override
+ public String[] getSupportedActivities() {
+ checkPermissions();
+ return mSupportedActivities;
+ }
+
+ @Override
+ public boolean isActivitySupported(String activity) {
+ checkPermissions();
+ int activityType = getActivityType(activity);
+ return activityType != INVALID_ACTIVITY_TYPE;
+ }
+
+ @Override
+ public boolean registerSink(IActivityRecognitionHardwareSink sink) {
+ checkPermissions();
+ return mSinks.register(sink);
+ }
+
+ @Override
+ public boolean unregisterSink(IActivityRecognitionHardwareSink sink) {
+ checkPermissions();
+ return mSinks.unregister(sink);
+ }
+
+ @Override
+ public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs) {
+ checkPermissions();
+
+ int activityType = getActivityType(activity);
+ if (activityType == INVALID_ACTIVITY_TYPE) {
+ return false;
+ }
+
+ int result = nativeEnableActivityEvent(activityType, eventType, reportLatencyNs);
+ if (result == NATIVE_SUCCESS_RESULT) {
+ mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_ENABLED;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean disableActivityEvent(String activity, int eventType) {
+ checkPermissions();
+
+ int activityType = getActivityType(activity);
+ if (activityType == INVALID_ACTIVITY_TYPE) {
+ return false;
+ }
+
+ int result = nativeDisableActivityEvent(activityType, eventType);
+ if (result == NATIVE_SUCCESS_RESULT) {
+ mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_DISABLED;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean flush() {
+ checkPermissions();
+ int result = nativeFlush();
+ return result == NATIVE_SUCCESS_RESULT;
+ }
+
+ /**
+ * Called by the Activity-Recognition HAL.
+ */
+ private void onActivityChanged(Event[] events) {
+ if (events == null || events.length == 0) {
+ if (DEBUG) Log.d(TAG, "No events to broadcast for onActivityChanged.");
+ return;
+ }
+
+ int eventsLength = events.length;
+ ActivityRecognitionEvent activityRecognitionEventArray[] =
+ new ActivityRecognitionEvent[eventsLength];
+ for (int i = 0; i < eventsLength; ++i) {
+ Event event = events[i];
+ String activityName = getActivityName(event.activity);
+ activityRecognitionEventArray[i] =
+ new ActivityRecognitionEvent(activityName, event.type, event.timestamp);
+ }
+ ActivityChangedEvent activityChangedEvent =
+ new ActivityChangedEvent(activityRecognitionEventArray);
+
+ int size = mSinks.beginBroadcast();
+ for (int i = 0; i < size; ++i) {
+ IActivityRecognitionHardwareSink sink = mSinks.getBroadcastItem(i);
+ try {
+ sink.onActivityChanged(activityChangedEvent);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error delivering activity changed event.", e);
+ }
+ }
+ mSinks.finishBroadcast();
+ }
+
+ private String getActivityName(int activityType) {
+ if (activityType < 0 || activityType >= mSupportedActivities.length) {
+ String message = String.format(
+ "Invalid ActivityType: %d, SupportedActivities: %d",
+ activityType,
+ mSupportedActivities.length);
+ Log.e(TAG, message);
+ return null;
+ }
+
+ return mSupportedActivities[activityType];
+ }
+
+ private int getActivityType(String activity) {
+ if (TextUtils.isEmpty(activity)) {
+ return INVALID_ACTIVITY_TYPE;
+ }
+
+ int supportedActivitiesLength = mSupportedActivities.length;
+ for (int i = 0; i < supportedActivitiesLength; ++i) {
+ if (activity.equals(mSupportedActivities[i])) {
+ return i;
+ }
+ }
+
+ return INVALID_ACTIVITY_TYPE;
+ }
+
+ private void checkPermissions() {
+ mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
+ }
+
+ private String[] fetchSupportedActivities() {
+ String[] supportedActivities = nativeGetSupportedActivities();
+ if (supportedActivities != null) {
+ return supportedActivities;
+ }
+
+ return new String[0];
+ }
+
+ private class SinkList extends RemoteCallbackList<IActivityRecognitionHardwareSink> {
+ @Override
+ public void onCallbackDied(IActivityRecognitionHardwareSink callback) {
+ int callbackCount = mSinks.getRegisteredCallbackCount();
+ if (DEBUG) Log.d(TAG, "RegisteredCallbackCount: " + callbackCount);
+ if (callbackCount != 0) {
+ return;
+ }
+ // currently there is only one client for this, so if all its sinks have died, we clean
+ // up after them, this ensures that the AR HAL is not out of sink
+ for (int activity = 0; activity < mSupportedActivitiesCount; ++activity) {
+ for (int event = 0; event < EVENT_TYPE_COUNT; ++event) {
+ disableActivityEventIfEnabled(activity, event);
+ }
+ }
+ }
+
+ private void disableActivityEventIfEnabled(int activityType, int eventType) {
+ if (mSupportedActivitiesEnabledEvents[activityType][eventType] != EVENT_TYPE_ENABLED) {
+ return;
+ }
+
+ int result = nativeDisableActivityEvent(activityType, eventType);
+ mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_DISABLED;
+ String message = String.format(
+ "DisableActivityEvent: activityType=%d, eventType=%d, result=%d",
+ activityType,
+ eventType,
+ result);
+ Log.e(TAG, message);
+ }
+ }
+
+ // native bindings
+ static { nativeClassInit(); }
+
+ private static native void nativeClassInit();
+ private static native boolean nativeIsSupported();
+
+ private native void nativeInitialize();
+ private native void nativeRelease();
+ private native String[] nativeGetSupportedActivities();
+ private native int nativeEnableActivityEvent(
+ int activityType,
+ int eventType,
+ long reportLatenceNs);
+ private native int nativeDisableActivityEvent(int activityType, int eventType);
+ private native int nativeFlush();
+}
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardware.aidl b/core/java/android/hardware/location/IActivityRecognitionHardware.aidl
new file mode 100644
index 0000000..bc6b183
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardware.aidl
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014, 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/license/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.hardware.location;
+
+import android.hardware.location.IActivityRecognitionHardwareSink;
+
+/**
+ * Activity Recognition Hardware provider interface.
+ * This interface can be used to implement hardware based activity recognition.
+ *
+ * @hide
+ */
+interface IActivityRecognitionHardware {
+ /**
+ * Gets an array of supported activities by hardware.
+ */
+ String[] getSupportedActivities();
+
+ /**
+ * Returns true if the given activity is supported, false otherwise.
+ */
+ boolean isActivitySupported(in String activityType);
+
+ /**
+ * Registers a sink with Hardware Activity-Recognition.
+ */
+ boolean registerSink(in IActivityRecognitionHardwareSink sink);
+
+ /**
+ * Unregisters a sink with Hardware Activity-Recognition.
+ */
+ boolean unregisterSink(in IActivityRecognitionHardwareSink sink);
+
+ /**
+ * Enables tracking of a given activity/event type, if the activity is supported.
+ */
+ boolean enableActivityEvent(in String activityType, int eventType, long reportLatencyNs);
+
+ /**
+ * Disables tracking of a given activity/eventy type.
+ */
+ boolean disableActivityEvent(in String activityType, int eventType);
+
+ /**
+ * Requests hardware for all the activity events detected up to the given point in time.
+ */
+ boolean flush();
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl
new file mode 100644
index 0000000..3fe645c
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015, 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/license/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.hardware.location;
+
+import android.hardware.location.IActivityRecognitionHardware;
+
+/**
+ * Activity Recognition Hardware client interface.
+ * This interface can be used to receive interfaces to implementations of
+ * {@link IActivityRecognitionHardware}.
+ *
+ * @hide
+ */
+oneway interface IActivityRecognitionHardwareClient {
+ /**
+ * Hardware Activity-Recognition availability event.
+ *
+ * @param isSupported whether the platform has hardware support for the feature
+ * @param instance the available instance to provide access to the feature
+ */
+ void onAvailabilityChanged(in boolean isSupported, in IActivityRecognitionHardware instance);
+}
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl
new file mode 100644
index 0000000..21c8e87
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014, 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/license/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.hardware.location;
+
+import android.hardware.location.ActivityChangedEvent;
+
+/**
+ * Activity Recognition Hardware provider Sink interface.
+ * This interface can be used to implement sinks to receive activity notifications.
+ *
+ * @hide
+ */
+interface IActivityRecognitionHardwareSink {
+ /**
+ * Activity changed event.
+ */
+ void onActivityChanged(in ActivityChangedEvent event);
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl
new file mode 100644
index 0000000..12e3117
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014, 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/license/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.hardware.location;
+
+import android.hardware.location.IActivityRecognitionHardware;
+
+/**
+ * Activity Recognition Hardware watcher. This interface can be used to receive interfaces to
+ * implementations of {@link IActivityRecognitionHardware}.
+ *
+ * @deprecated use {@link IActivityRecognitionHardwareClient} instead.
+
+ * @hide
+ */
+interface IActivityRecognitionHardwareWatcher {
+ /**
+ * Hardware Activity-Recognition availability event.
+ */
+ void onInstanceChanged(in IActivityRecognitionHardware instance);
+}
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index edc3f94..299a00a 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -120,6 +120,9 @@
/* Sets the port's current role. */
void setPortRoles(in String portId, int powerRole, int dataRole);
+ /* Enable/disable contaminant detection */
+ void enableContaminantDetection(in String portId, boolean enable);
+
/* Sets USB device connection handler. */
void setUsbDeviceConnectionHandler(in ComponentName usbDeviceConnectionHandler);
}
diff --git a/core/java/android/hardware/usb/ParcelableUsbPort.java b/core/java/android/hardware/usb/ParcelableUsbPort.java
index 7f7ba96..30388af 100644
--- a/core/java/android/hardware/usb/ParcelableUsbPort.java
+++ b/core/java/android/hardware/usb/ParcelableUsbPort.java
@@ -31,10 +31,21 @@
public final class ParcelableUsbPort implements Parcelable {
private final @NonNull String mId;
private final int mSupportedModes;
+ private final int mSupportedContaminantProtectionModes;
+ private final boolean mSupportsEnableContaminantPresenceProtection;
+ private final boolean mSupportsEnableContaminantPresenceDetection;
- private ParcelableUsbPort(@NonNull String id, int supportedModes) {
+ private ParcelableUsbPort(@NonNull String id, int supportedModes,
+ int supportedContaminantProtectionModes,
+ boolean supportsEnableContaminantPresenceProtection,
+ boolean supportsEnableContaminantPresenceDetection) {
mId = id;
mSupportedModes = supportedModes;
+ mSupportedContaminantProtectionModes = supportedContaminantProtectionModes;
+ mSupportsEnableContaminantPresenceProtection =
+ supportsEnableContaminantPresenceProtection;
+ mSupportsEnableContaminantPresenceDetection =
+ supportsEnableContaminantPresenceDetection;
}
/**
@@ -45,7 +56,10 @@
* @return The parcelable version of the port
*/
public static @NonNull ParcelableUsbPort of(@NonNull UsbPort port) {
- return new ParcelableUsbPort(port.getId(), port.getSupportedModes());
+ return new ParcelableUsbPort(port.getId(), port.getSupportedModes(),
+ port.getSupportedContaminantProtectionModes(),
+ port.supportsEnableContaminantPresenceProtection(),
+ port.supportsEnableContaminantPresenceDetection());
}
/**
@@ -56,7 +70,9 @@
* @return The UsbPort for this object
*/
public @NonNull UsbPort getUsbPort(@NonNull UsbManager usbManager) {
- return new UsbPort(usbManager, mId, mSupportedModes);
+ return new UsbPort(usbManager, mId, mSupportedModes, mSupportedContaminantProtectionModes,
+ mSupportsEnableContaminantPresenceProtection,
+ mSupportsEnableContaminantPresenceDetection);
}
@Override
@@ -68,6 +84,9 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mId);
dest.writeInt(mSupportedModes);
+ dest.writeInt(mSupportedContaminantProtectionModes);
+ dest.writeBoolean(mSupportsEnableContaminantPresenceProtection);
+ dest.writeBoolean(mSupportsEnableContaminantPresenceDetection);
}
public static final Creator<ParcelableUsbPort> CREATOR =
@@ -76,7 +95,14 @@
public ParcelableUsbPort createFromParcel(Parcel in) {
String id = in.readString();
int supportedModes = in.readInt();
- return new ParcelableUsbPort(id, supportedModes);
+ int supportedContaminantProtectionModes = in.readInt();
+ boolean supportsEnableContaminantPresenceProtection = in.readBoolean();
+ boolean supportsEnableContaminantPresenceDetection = in.readBoolean();
+
+ return new ParcelableUsbPort(id, supportedModes,
+ supportedContaminantProtectionModes,
+ supportsEnableContaminantPresenceProtection,
+ supportsEnableContaminantPresenceDetection);
}
@Override
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 6014478..eb148b9 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -854,6 +854,20 @@
}
/**
+ * Enables USB port contaminant detection algorithm.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_USB)
+ void enableContaminantDetection(@NonNull UsbPort port, boolean enable) {
+ try {
+ mService.enableContaminantDetection(port.getId(), enable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Sets the component that will handle USB device connection.
* <p>
* Setting component allows to specify external USB host manager to handle use cases, where
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index 37154e4..c674480 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -16,6 +16,10 @@
package android.hardware.usb;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_DETECTED;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_DISABLED;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST;
import static android.hardware.usb.UsbPortStatus.DATA_ROLE_NONE;
@@ -48,16 +52,21 @@
private final String mId;
private final int mSupportedModes;
private final UsbManager mUsbManager;
+ private final int mSupportedContaminantProtectionModes;
+ private final boolean mSupportsEnableContaminantPresenceProtection;
+ private final boolean mSupportsEnableContaminantPresenceDetection;
private static final int NUM_DATA_ROLES = Constants.PortDataRole.NUM_DATA_ROLES;
-
/**
* Points to the first power role in the IUsb HAL.
*/
private static final int POWER_ROLE_OFFSET = Constants.PortPowerRole.NONE;
/** @hide */
- public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes) {
+ public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes,
+ int supportedContaminantProtectionModes,
+ boolean supportsEnableContaminantPresenceProtection,
+ boolean supportsEnableContaminantPresenceDetection) {
Preconditions.checkNotNull(id);
Preconditions.checkFlagsArgument(supportedModes,
MODE_DFP | MODE_UFP | MODE_AUDIO_ACCESSORY | MODE_DEBUG_ACCESSORY);
@@ -65,6 +74,11 @@
mUsbManager = usbManager;
mId = id;
mSupportedModes = supportedModes;
+ mSupportedContaminantProtectionModes = supportedContaminantProtectionModes;
+ mSupportsEnableContaminantPresenceProtection =
+ supportsEnableContaminantPresenceProtection;
+ mSupportsEnableContaminantPresenceDetection =
+ supportsEnableContaminantPresenceDetection;
}
/**
@@ -93,6 +107,36 @@
return mSupportedModes;
}
+ /**
+ * Gets the supported port proctection modes when the port is contaminated.
+ * <p>
+ * The actual mode of the port is decided by the hardware
+ * </p>
+ *
+ * @hide
+ */
+ public int getSupportedContaminantProtectionModes() {
+ return mSupportedContaminantProtectionModes;
+ }
+
+ /**
+ * Tells if UsbService can enable/disable contaminant presence protection.
+ *
+ * @hide
+ */
+ public boolean supportsEnableContaminantPresenceProtection() {
+ return mSupportsEnableContaminantPresenceProtection;
+ }
+
+ /**
+ * Tells if UsbService can enable/disable contaminant presence detection.
+ *
+ * @hide
+ */
+ public boolean supportsEnableContaminantPresenceDetection() {
+ return mSupportsEnableContaminantPresenceDetection;
+ }
+
/**
* Gets the status of this USB port.
*
@@ -131,6 +175,12 @@
}
/**
+ * @hide
+ **/
+ public void enableContaminantDetection(boolean enable) {
+ mUsbManager.enableContaminantDetection(this, enable);
+ }
+ /**
* Combines one power and one data role together into a unique value with
* exactly one bit set. This can be used to efficiently determine whether
* a combination of roles is supported by testing whether that bit is present
@@ -206,6 +256,22 @@
}
/** @hide */
+ public static String contaminantPresenceStatusToString(int contaminantPresenceStatus) {
+ switch (contaminantPresenceStatus) {
+ case CONTAMINANT_DETECTION_NOT_SUPPORTED:
+ return "not-supported";
+ case CONTAMINANT_DETECTION_DISABLED:
+ return "disabled";
+ case CONTAMINANT_DETECTION_DETECTED:
+ return "detected";
+ case CONTAMINANT_DETECTION_NOT_DETECTED:
+ return "not detected";
+ default:
+ return Integer.toString(contaminantPresenceStatus);
+ }
+ }
+
+ /** @hide */
public static String roleCombinationsToString(int combo) {
StringBuilder result = new StringBuilder();
result.append("[");
@@ -264,6 +330,11 @@
@Override
public String toString() {
- return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes) + "}";
+ return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes)
+ + "supportedContaminantProtectionModes=" + mSupportedContaminantProtectionModes
+ + "supportsEnableContaminantPresenceProtection="
+ + mSupportsEnableContaminantPresenceProtection
+ + "supportsEnableContaminantPresenceDetection="
+ + mSupportsEnableContaminantPresenceDetection;
}
}
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index d30201a..426dba8 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -39,6 +39,8 @@
private final @UsbPowerRole int mCurrentPowerRole;
private final @UsbDataRole int mCurrentDataRole;
private final int mSupportedRoleCombinations;
+ private final @ContaminantProtectionStatus int mContaminantProtectionStatus;
+ private final @ContaminantDetectionStatus int mContaminantDetectionStatus;
/**
* Power role: This USB port does not have a power role.
@@ -131,6 +133,93 @@
public static final int MODE_DEBUG_ACCESSORY =
android.hardware.usb.V1_1.Constants.PortMode_1_1.DEBUG_ACCESSORY;
+ /**
+ * Contaminant presence detection not supported by the device.
+ * @hide
+ */
+ public static final int CONTAMINANT_DETECTION_NOT_SUPPORTED =
+ android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.NOT_SUPPORTED;
+
+ /**
+ * Contaminant presence detection supported but disabled.
+ * @hide
+ */
+ public static final int CONTAMINANT_DETECTION_DISABLED =
+ android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.DISABLED;
+
+ /**
+ * Contaminant presence enabled but not detected.
+ * @hide
+ */
+ public static final int CONTAMINANT_DETECTION_NOT_DETECTED =
+ android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.NOT_DETECTED;
+
+ /**
+ * Contaminant presence enabled and detected.
+ * @hide
+ */
+ public static final int CONTAMINANT_DETECTION_DETECTED =
+ android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.DETECTED;
+
+ /**
+ * Contaminant protection - No action performed upon detection of
+ * contaminant presence.
+ * @hide
+ */
+ public static final int CONTAMINANT_PROTECTION_NONE =
+ android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.NONE;
+
+ /**
+ * Contaminant protection - Port is forced to sink upon detection of
+ * contaminant presence.
+ * @hide
+ */
+ public static final int CONTAMINANT_PROTECTION_SINK =
+ android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_SINK;
+
+ /**
+ * Contaminant protection - Port is forced to source upon detection of
+ * contaminant presence.
+ * @hide
+ */
+ public static final int CONTAMINANT_PROTECTION_SOURCE =
+ android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_SOURCE;
+
+ /**
+ * Contaminant protection - Port is disabled upon detection of
+ * contaminant presence.
+ * @hide
+ */
+ public static final int CONTAMINANT_PROTECTION_FORCE_DISABLE =
+ android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_DISABLE;
+
+ /**
+ * Contaminant protection - Port is disabled upon detection of
+ * contaminant presence.
+ * @hide
+ */
+ public static final int CONTAMINANT_PROTECTION_DISABLED =
+ android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.DISABLED;
+
+ @IntDef(prefix = { "CONTAMINANT_DETECION_" }, flag = true, value = {
+ CONTAMINANT_DETECTION_NOT_SUPPORTED,
+ CONTAMINANT_DETECTION_DISABLED,
+ CONTAMINANT_DETECTION_NOT_DETECTED,
+ CONTAMINANT_DETECTION_DETECTED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ContaminantDetectionStatus{}
+
+ @IntDef(prefix = { "CONTAMINANT_PROTECTION_" }, flag = true, value = {
+ CONTAMINANT_PROTECTION_NONE,
+ CONTAMINANT_PROTECTION_SINK,
+ CONTAMINANT_PROTECTION_SOURCE,
+ CONTAMINANT_PROTECTION_FORCE_DISABLE,
+ CONTAMINANT_PROTECTION_DISABLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ContaminantProtectionStatus{}
+
@IntDef(prefix = { "MODE_" }, flag = true, value = {
MODE_NONE,
MODE_DFP,
@@ -142,12 +231,15 @@
@interface UsbPortMode{}
/** @hide */
- public UsbPortStatus(int currentMode, @UsbPowerRole int currentPowerRole,
- @UsbDataRole int currentDataRole, int supportedRoleCombinations) {
+ public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole,
+ int supportedRoleCombinations, int contaminantProtectionStatus,
+ int contaminantDetectionStatus) {
mCurrentMode = currentMode;
mCurrentPowerRole = currentPowerRole;
mCurrentDataRole = currentDataRole;
mSupportedRoleCombinations = supportedRoleCombinations;
+ mContaminantProtectionStatus = contaminantProtectionStatus;
+ mContaminantDetectionStatus = contaminantDetectionStatus;
}
/**
@@ -212,6 +304,24 @@
return mSupportedRoleCombinations;
}
+ /**
+ * Returns contaminant detection status.
+ *
+ * @hide
+ */
+ public @ContaminantDetectionStatus int getContaminantDetectionStatus() {
+ return mContaminantDetectionStatus;
+ }
+
+ /**
+ * Returns contamiant protection status.
+ *
+ * @hide
+ */
+ public @ContaminantProtectionStatus int getContaminantProtectionStatus() {
+ return mContaminantProtectionStatus;
+ }
+
@Override
public String toString() {
return "UsbPortStatus{connected=" + isConnected()
@@ -220,6 +330,10 @@
+ ", currentDataRole=" + UsbPort.dataRoleToString(mCurrentDataRole)
+ ", supportedRoleCombinations="
+ UsbPort.roleCombinationsToString(mSupportedRoleCombinations)
+ + ", contaminantDetectionStatus="
+ + getContaminantDetectionStatus()
+ + ", contaminantProtectionStatus="
+ + getContaminantProtectionStatus()
+ "}";
}
@@ -234,6 +348,8 @@
dest.writeInt(mCurrentPowerRole);
dest.writeInt(mCurrentDataRole);
dest.writeInt(mSupportedRoleCombinations);
+ dest.writeInt(mContaminantProtectionStatus);
+ dest.writeInt(mContaminantDetectionStatus);
}
public static final Parcelable.Creator<UsbPortStatus> CREATOR =
@@ -244,8 +360,11 @@
int currentPowerRole = in.readInt();
int currentDataRole = in.readInt();
int supportedRoleCombinations = in.readInt();
+ int contaminantProtectionStatus = in.readInt();
+ int contaminantDetectionStatus = in.readInt();
return new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
- supportedRoleCombinations);
+ supportedRoleCombinations, contaminantProtectionStatus,
+ contaminantDetectionStatus);
}
@Override
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index d3509d5..38ddc16 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -2087,7 +2087,14 @@
* protocol, so applications with custom text editing written before this method appeared will
* not call to inform the IME of this interaction.
* @param focusChanged true if the user changed the focused view by this click.
+ * @see InputMethodManager#viewClicked(View)
+ * @deprecated The method may not be called for composite {@link View} that works as a giant
+ * "Canvas", which can host its own UI hierarchy and sub focus state.
+ * {@link android.webkit.WebView} is a good example. Application / IME developers
+ * should not rely on this method. If your goal is just being notified when an
+ * on-going input is interrupted, simply monitor {@link #onFinishInput()}.
*/
+ @Deprecated
public void onViewClicked(boolean focusChanged) {
// Intentionally empty
}
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
index 4047068..3b01266 100644
--- a/core/java/android/net/CaptivePortal.java
+++ b/core/java/android/net/CaptivePortal.java
@@ -45,6 +45,8 @@
private final IBinder mBinder;
/** @hide */
+ @SystemApi
+ @TestApi
public CaptivePortal(IBinder binder) {
mBinder = binder;
}
@@ -107,6 +109,8 @@
* connectivity for apps because the captive portal is still in place.
* @hide
*/
+ @SystemApi
+ @TestApi
public void useNetwork() {
try {
ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_WANTED_AS_IS);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index cee3a40..5bb24ba 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -15,6 +15,9 @@
*/
package android.net;
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,6 +31,8 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.SocketKeepalive.Callback;
import android.os.Binder;
import android.os.Build;
import android.os.Build.VERSION_CODES;
@@ -58,6 +63,7 @@
import libcore.net.event.NetworkEventDispatcher;
+import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
@@ -66,6 +72,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
/**
* Class that answers queries about the state of network connectivity. It also
@@ -1699,6 +1706,8 @@
* {@link PacketKeepaliveCallback#onStopped} if the operation was successful or
* {@link PacketKeepaliveCallback#onError} if an error occurred.
*
+ * @deprecated Use {@link SocketKeepalive} instead.
+ *
* @hide
*/
public class PacketKeepalive {
@@ -1802,6 +1811,8 @@
/**
* Starts an IPsec NAT-T keepalive packet with the specified parameters.
*
+ * @deprecated Use {@link #createSocketKeepalive} instead.
+ *
* @hide
*/
@UnsupportedAppUsage
@@ -1821,6 +1832,62 @@
}
/**
+ * Request that keepalives be started on a IPsec NAT-T socket.
+ *
+ * @param network The {@link Network} the socket is on.
+ * @param socket The socket that needs to be kept alive.
+ * @param source The source address of the {@link UdpEncapsulationSocket}.
+ * @param destination The destination address of the {@link UdpEncapsulationSocket}.
+ * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+ * must run callback sequentially, otherwise the order of callbacks cannot be
+ * guaranteed.
+ * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+ * changes. Must be extended by applications that use this API.
+ *
+ * @return A {@link SocketKeepalive} object, which can be used to control this keepalive object.
+ **/
+ public SocketKeepalive createSocketKeepalive(@NonNull Network network,
+ @NonNull UdpEncapsulationSocket socket,
+ @NonNull InetAddress source,
+ @NonNull InetAddress destination,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Callback callback) {
+ return new NattSocketKeepalive(mService, network, socket.getFileDescriptor(),
+ socket.getResourceId(), source, destination, executor, callback);
+ }
+
+ /**
+ * Request that keepalives be started on a IPsec NAT-T socket file descriptor. Directly called
+ * by system apps which don't use IpSecService to create {@link UdpEncapsulationSocket}.
+ *
+ * @param network The {@link Network} the socket is on.
+ * @param fd The {@link FileDescriptor} that needs to be kept alive. The provided
+ * {@link FileDescriptor} must be bound to a port and the keepalives will be sent from
+ * that port.
+ * @param source The source address of the {@link UdpEncapsulationSocket}.
+ * @param destination The destination address of the {@link UdpEncapsulationSocket}. The
+ * keepalive packets will always be sent to port 4500 of the given {@code destination}.
+ * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+ * must run callback sequentially, otherwise the order of callbacks cannot be
+ * guaranteed.
+ * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+ * changes. Must be extended by applications that use this API.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD)
+ public SocketKeepalive createNattKeepalive(@NonNull Network network,
+ @NonNull FileDescriptor fd,
+ @NonNull InetAddress source,
+ @NonNull InetAddress destination,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Callback callback) {
+ return new NattSocketKeepalive(mService, network, fd, INVALID_RESOURCE_ID /* Unused */,
+ source, destination, executor, callback);
+ }
+
+ /**
* Ensure that a network route exists to deliver traffic to the specified
* host via the specified network interface. An attempt to add a route that
* already exists is ignored, but treated as successful.
@@ -3699,6 +3766,19 @@
}
/**
+ * Determine whether the device is configured to avoid bad wifi.
+ * @hide
+ */
+ @SystemApi
+ public boolean getAvoidBadWifi() {
+ try {
+ return mService.getAvoidBadWifi();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* It is acceptable to briefly use multipath data to provide seamless connectivity for
* time-sensitive user-facing operations when the system default network is temporarily
* unresponsive. The amount of data should be limited (less than one megabyte for every call to
diff --git a/core/java/android/net/DnsPacket.java b/core/java/android/net/DnsPacket.java
new file mode 100644
index 0000000..458fb34
--- /dev/null
+++ b/core/java/android/net/DnsPacket.java
@@ -0,0 +1,235 @@
+/*
+ * 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.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.internal.util.BitUtils;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+/**
+ * Defines basic data for DNS protocol based on RFC 1035.
+ * Subclasses create the specific format used in DNS packet.
+ *
+ * @hide
+ */
+public abstract class DnsPacket {
+ public class DnsHeader {
+ private static final String TAG = "DnsHeader";
+ public final int id;
+ public final int flags;
+ public final int rcode;
+ private final int[] mSectionCount;
+
+ /**
+ * Create a new DnsHeader from a positioned ByteBuffer.
+ *
+ * The ByteBuffer must be in network byte order (which is the default).
+ * Reads the passed ByteBuffer from its current position and decodes a DNS header.
+ * When this constructor returns, the reading position of the ByteBuffer has been
+ * advanced to the end of the DNS header record.
+ * This is meant to chain with other methods reading a DNS response in sequence.
+ *
+ */
+ DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException {
+ id = BitUtils.uint16(buf.getShort());
+ flags = BitUtils.uint16(buf.getShort());
+ rcode = flags & 0xF;
+ mSectionCount = new int[NUM_SECTIONS];
+ for (int i = 0; i < NUM_SECTIONS; ++i) {
+ mSectionCount[i] = BitUtils.uint16(buf.getShort());
+ }
+ }
+
+ /**
+ * Get section count by section type.
+ */
+ public int getSectionCount(int sectionType) {
+ return mSectionCount[sectionType];
+ }
+ }
+
+ public class DnsSection {
+ private static final int MAXNAMESIZE = 255;
+ private static final int MAXLABELSIZE = 63;
+ private static final int MAXLABELCOUNT = 128;
+ private static final int NAME_NORMAL = 0;
+ private static final int NAME_COMPRESSION = 0xC0;
+ private final DecimalFormat byteFormat = new DecimalFormat();
+ private final FieldPosition pos = new FieldPosition(0);
+
+ private static final String TAG = "DnsSection";
+
+ public final String dName;
+ public final int nsType;
+ public final int nsClass;
+ public final long ttl;
+ private final byte[] mRR;
+
+ /**
+ * Create a new DnsSection from a positioned ByteBuffer.
+ *
+ * The ByteBuffer must be in network byte order (which is the default).
+ * Reads the passed ByteBuffer from its current position and decodes a DNS section.
+ * When this constructor returns, the reading position of the ByteBuffer has been
+ * advanced to the end of the DNS header record.
+ * This is meant to chain with other methods reading a DNS response in sequence.
+ *
+ */
+ DnsSection(int sectionType, @NonNull ByteBuffer buf)
+ throws BufferUnderflowException, ParseException {
+ dName = parseName(buf, 0 /* Parse depth */);
+ if (dName.length() > MAXNAMESIZE) {
+ throw new ParseException("Parse name fail, name size is too long");
+ }
+ nsType = BitUtils.uint16(buf.getShort());
+ nsClass = BitUtils.uint16(buf.getShort());
+
+ if (sectionType != QDSECTION) {
+ ttl = BitUtils.uint32(buf.getInt());
+ final int length = BitUtils.uint16(buf.getShort());
+ mRR = new byte[length];
+ buf.get(mRR);
+ } else {
+ ttl = 0;
+ mRR = null;
+ }
+ }
+
+ /**
+ * Get a copy of rr.
+ */
+ @Nullable public byte[] getRR() {
+ return (mRR == null) ? null : mRR.clone();
+ }
+
+ /**
+ * Convert label from {@code byte[]} to {@code String}
+ *
+ * It follows the same converting rule as native layer.
+ * (See ns_name.c in libc)
+ *
+ */
+ private String labelToString(@NonNull byte[] label) {
+ final StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < label.length; ++i) {
+ int b = BitUtils.uint8(label[i]);
+ // Control characters and non-ASCII characters.
+ if (b <= 0x20 || b >= 0x7f) {
+ sb.append('\\');
+ byteFormat.format(b, sb, pos);
+ } else if (b == '"' || b == '.' || b == ';' || b == '\\'
+ || b == '(' || b == ')' || b == '@' || b == '$') {
+ sb.append('\\');
+ sb.append((char) b);
+ } else {
+ sb.append((char) b);
+ }
+ }
+ return sb.toString();
+ }
+
+ private String parseName(@NonNull ByteBuffer buf, int depth) throws
+ BufferUnderflowException, ParseException {
+ if (depth > MAXLABELCOUNT) throw new ParseException("Parse name fails, too many labels");
+ final int len = BitUtils.uint8(buf.get());
+ final int mask = len & NAME_COMPRESSION;
+ if (0 == len) {
+ return "";
+ } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) {
+ throw new ParseException("Parse name fail, bad label type");
+ } else if (mask == NAME_COMPRESSION) {
+ // Name compression based on RFC 1035 - 4.1.4 Message compression
+ final int offset = ((len & ~NAME_COMPRESSION) << 8) + BitUtils.uint8(buf.get());
+ final int oldPos = buf.position();
+ if (offset >= oldPos - 2) {
+ throw new ParseException("Parse compression name fail, invalid compression");
+ }
+ buf.position(offset);
+ final String pointed = parseName(buf, depth + 1);
+ buf.position(oldPos);
+ return pointed;
+ } else {
+ final byte[] label = new byte[len];
+ buf.get(label);
+ final String head = labelToString(label);
+ if (head.length() > MAXLABELSIZE) {
+ throw new ParseException("Parse name fail, invalid label length");
+ }
+ final String tail = parseName(buf, depth + 1);
+ return TextUtils.isEmpty(tail) ? head : head + "." + tail;
+ }
+ }
+ }
+
+ public static final int QDSECTION = 0;
+ public static final int ANSECTION = 1;
+ public static final int NSSECTION = 2;
+ public static final int ARSECTION = 3;
+ private static final int NUM_SECTIONS = ARSECTION + 1;
+
+ private static final String TAG = DnsPacket.class.getSimpleName();
+
+ protected final DnsHeader mHeader;
+ protected final List<DnsSection>[] mSections;
+
+ public static class ParseException extends Exception {
+ public ParseException(String msg) {
+ super(msg);
+ }
+
+ public ParseException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+ }
+
+ protected DnsPacket(@NonNull byte[] data) throws ParseException {
+ if (null == data) throw new ParseException("Parse header failed, null input data");
+ final ByteBuffer buffer;
+ try {
+ buffer = ByteBuffer.wrap(data);
+ mHeader = new DnsHeader(buffer);
+ } catch (BufferUnderflowException e) {
+ throw new ParseException("Parse Header fail, bad input data", e);
+ }
+
+ mSections = new ArrayList[NUM_SECTIONS];
+
+ for (int i = 0; i < NUM_SECTIONS; ++i) {
+ final int count = mHeader.getSectionCount(i);
+ if (count > 0) {
+ mSections[i] = new ArrayList(count);
+ }
+ for (int j = 0; j < count; ++j) {
+ try {
+ mSections[i].add(new DnsSection(i, buffer));
+ } catch (BufferUnderflowException e) {
+ throw new ParseException("Parse section fail", e);
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java
new file mode 100644
index 0000000..6d54264
--- /dev/null
+++ b/core/java/android/net/DnsResolver.java
@@ -0,0 +1,289 @@
+/*
+ * 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.net;
+
+import static android.net.NetworkUtils.resNetworkQuery;
+import static android.net.NetworkUtils.resNetworkResult;
+import static android.net.NetworkUtils.resNetworkSend;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.MessageQueue;
+import android.system.ErrnoException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+
+/**
+ * Dns resolver class for asynchronous dns querying
+ *
+ */
+public final class DnsResolver {
+ private static final String TAG = "DnsResolver";
+ private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
+ private static final int MAXPACKET = 8 * 1024;
+
+ @IntDef(prefix = { "CLASS_" }, value = {
+ CLASS_IN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface QueryClass {}
+ public static final int CLASS_IN = 1;
+
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_A,
+ TYPE_AAAA
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface QueryType {}
+ public static final int TYPE_A = 1;
+ public static final int TYPE_AAAA = 28;
+
+ @IntDef(prefix = { "FLAG_" }, value = {
+ FLAG_EMPTY,
+ FLAG_NO_RETRY,
+ FLAG_NO_CACHE_STORE,
+ FLAG_NO_CACHE_LOOKUP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface QueryFlag {}
+ public static final int FLAG_EMPTY = 0;
+ public static final int FLAG_NO_RETRY = 1 << 0;
+ public static final int FLAG_NO_CACHE_STORE = 1 << 1;
+ public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2;
+
+ private static final int DNS_RAW_RESPONSE = 1;
+
+ private static final int NETID_UNSET = 0;
+
+ private static final DnsResolver sInstance = new DnsResolver();
+
+ /**
+ * listener for receiving raw answers
+ */
+ public interface RawAnswerListener {
+ /**
+ * {@code byte[]} is {@code null} if query timed out
+ */
+ void onAnswer(@Nullable byte[] answer);
+ }
+
+ /**
+ * listener for receiving parsed answers
+ */
+ public interface InetAddressAnswerListener {
+ /**
+ * Will be called exactly once with all the answers to the query.
+ * size of addresses will be zero if no available answer could be parsed.
+ */
+ void onAnswer(@NonNull List<InetAddress> addresses);
+ }
+
+ /**
+ * Get instance for DnsResolver
+ */
+ public static DnsResolver getInstance() {
+ return sInstance;
+ }
+
+ private DnsResolver() {}
+
+ /**
+ * Pass in a blob and corresponding setting,
+ * get a blob back asynchronously with the entire raw answer.
+ *
+ * @param network {@link Network} specifying which network for querying.
+ * {@code null} for query on default network.
+ * @param query blob message
+ * @param flags flags as a combination of the FLAGS_* constants
+ * @param handler {@link Handler} to specify the thread
+ * upon which the {@link RawAnswerListener} will be invoked.
+ * @param listener a {@link RawAnswerListener} which will be called to notify the caller
+ * of the result of dns query.
+ */
+ public void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
+ @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
+ final FileDescriptor queryfd = resNetworkSend((network != null
+ ? network.netId : NETID_UNSET), query, query.length, flags);
+ registerFDListener(handler.getLooper().getQueue(), queryfd,
+ answerbuf -> listener.onAnswer(answerbuf));
+ }
+
+ /**
+ * Pass in a domain name and corresponding setting,
+ * get a blob back asynchronously with the entire raw answer.
+ *
+ * @param network {@link Network} specifying which network for querying.
+ * {@code null} for query on default network.
+ * @param domain domain name for querying
+ * @param nsClass dns class as one of the CLASS_* constants
+ * @param nsType dns resource record (RR) type as one of the TYPE_* constants
+ * @param flags flags as a combination of the FLAGS_* constants
+ * @param handler {@link Handler} to specify the thread
+ * upon which the {@link RawAnswerListener} will be invoked.
+ * @param listener a {@link RawAnswerListener} which will be called to notify the caller
+ * of the result of dns query.
+ */
+ public void query(@Nullable Network network, @NonNull String domain, @QueryClass int nsClass,
+ @QueryType int nsType, @QueryFlag int flags,
+ @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
+ final FileDescriptor queryfd = resNetworkQuery((network != null
+ ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags);
+ registerFDListener(handler.getLooper().getQueue(), queryfd,
+ answerbuf -> listener.onAnswer(answerbuf));
+ }
+
+ /**
+ * Pass in a domain name and corresponding setting,
+ * get back a set of InetAddresses asynchronously.
+ *
+ * @param network {@link Network} specifying which network for querying.
+ * {@code null} for query on default network.
+ * @param domain domain name for querying
+ * @param flags flags as a combination of the FLAGS_* constants
+ * @param handler {@link Handler} to specify the thread
+ * upon which the {@link InetAddressAnswerListener} will be invoked.
+ * @param listener an {@link InetAddressAnswerListener} which will be called to
+ * notify the caller of the result of dns query.
+ *
+ */
+ public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags,
+ @NonNull Handler handler, @NonNull InetAddressAnswerListener listener)
+ throws ErrnoException {
+ final FileDescriptor v4fd = resNetworkQuery((network != null
+ ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags);
+ final FileDescriptor v6fd = resNetworkQuery((network != null
+ ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags);
+
+ final InetAddressAnswerAccumulator accmulator =
+ new InetAddressAnswerAccumulator(2, listener);
+ final Consumer<byte[]> consumer = answerbuf ->
+ accmulator.accumulate(parseAnswers(answerbuf));
+
+ registerFDListener(handler.getLooper().getQueue(), v4fd, consumer);
+ registerFDListener(handler.getLooper().getQueue(), v6fd, consumer);
+ }
+
+ private void registerFDListener(@NonNull MessageQueue queue,
+ @NonNull FileDescriptor queryfd, @NonNull Consumer<byte[]> answerConsumer) {
+ queue.addOnFileDescriptorEventListener(
+ queryfd,
+ FD_EVENTS,
+ (fd, events) -> {
+ byte[] answerbuf = null;
+ try {
+ // TODO: Implement result function in Java side instead of using JNI
+ // Because JNI method close fd prior than unregistering fd on
+ // event listener.
+ answerbuf = resNetworkResult(fd);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "resNetworkResult:" + e.toString());
+ }
+ answerConsumer.accept(answerbuf);
+
+ // Unregister this fd listener
+ return 0;
+ });
+ }
+
+ private class DnsAddressAnswer extends DnsPacket {
+ private static final String TAG = "DnsResolver.DnsAddressAnswer";
+ private static final boolean DBG = false;
+
+ private final int mQueryType;
+
+ DnsAddressAnswer(@NonNull byte[] data) throws ParseException {
+ super(data);
+ if ((mHeader.flags & (1 << 15)) == 0) {
+ throw new ParseException("Not an answer packet");
+ }
+ if (mHeader.rcode != 0) {
+ throw new ParseException("Response error, rcode:" + mHeader.rcode);
+ }
+ if (mHeader.getSectionCount(ANSECTION) == 0) {
+ throw new ParseException("No available answer");
+ }
+ if (mHeader.getSectionCount(QDSECTION) == 0) {
+ throw new ParseException("No question found");
+ }
+ // Assume only one question per answer packet. (RFC1035)
+ mQueryType = mSections[QDSECTION].get(0).nsType;
+ }
+
+ public @NonNull List<InetAddress> getAddresses() {
+ final List<InetAddress> results = new ArrayList<InetAddress>();
+ for (final DnsSection ansSec : mSections[ANSECTION]) {
+ // Only support A and AAAA, also ignore answers if query type != answer type.
+ int nsType = ansSec.nsType;
+ if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) {
+ continue;
+ }
+ try {
+ results.add(InetAddress.getByAddress(ansSec.getRR()));
+ } catch (UnknownHostException e) {
+ if (DBG) {
+ Log.w(TAG, "rr to address fail");
+ }
+ }
+ }
+ return results;
+ }
+ }
+
+ private @Nullable List<InetAddress> parseAnswers(@Nullable byte[] data) {
+ try {
+ return (data == null) ? null : new DnsAddressAnswer(data).getAddresses();
+ } catch (DnsPacket.ParseException e) {
+ Log.e(TAG, "Parse answer fail " + e.getMessage());
+ return null;
+ }
+ }
+
+ private class InetAddressAnswerAccumulator {
+ private final List<InetAddress> mAllAnswers;
+ private final InetAddressAnswerListener mAnswerListener;
+ private final int mTargetAnswerCount;
+ private int mReceivedAnswerCount = 0;
+
+ InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerListener listener) {
+ mTargetAnswerCount = size;
+ mAllAnswers = new ArrayList<>();
+ mAnswerListener = listener;
+ }
+
+ public void accumulate(@Nullable List<InetAddress> answer) {
+ if (null != answer) {
+ mAllAnswers.addAll(answer);
+ }
+ if (++mReceivedAnswerCount == mTargetAnswerCount) {
+ mAnswerListener.onAnswer(mAllAnswers);
+ }
+ }
+ }
+}
diff --git a/core/java/android/net/ICaptivePortal.aidl b/core/java/android/net/ICaptivePortal.aidl
index a013e79..56ae57d 100644
--- a/core/java/android/net/ICaptivePortal.aidl
+++ b/core/java/android/net/ICaptivePortal.aidl
@@ -20,7 +20,6 @@
* Interface to inform NetworkMonitor of decisions of app handling captive portal.
* @hide
*/
-interface ICaptivePortal
-{
- oneway void appResponse(int response);
+oneway interface ICaptivePortal {
+ void appResponse(int response);
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index da5d96e..e97060a 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -165,6 +165,7 @@
void setAvoidUnvalidated(in Network network);
void startCaptivePortalApp(in Network network);
+ boolean getAvoidBadWifi();
int getMultipathPreference(in Network Network);
NetworkRequest getDefaultRequest();
@@ -180,6 +181,10 @@
void startNattKeepalive(in Network network, int intervalSeconds, in Messenger messenger,
in IBinder binder, String srcAddr, int srcPort, String dstAddr);
+ void startNattKeepaliveWithFd(in Network network, in FileDescriptor fd, int resourceId,
+ int intervalSeconds, in Messenger messenger, in IBinder binder, String srcAddr,
+ String dstAddr);
+
void stopKeepalive(in Network network, int slot);
String getCaptivePortalServerUrl();
@@ -187,4 +192,6 @@
byte[] getNetworkWatchlistConfigHash();
int getConnectionOwnerUid(in ConnectionInfo connectionInfo);
+ boolean isCallerCurrentAlwaysOnVpnApp();
+ boolean isCallerCurrentAlwaysOnVpnLockdownApp();
}
diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl
index b7af374..f0fe92e 100644
--- a/core/java/android/net/INetworkManagementEventObserver.aidl
+++ b/core/java/android/net/INetworkManagementEventObserver.aidl
@@ -24,7 +24,7 @@
*
* @hide
*/
-interface INetworkManagementEventObserver {
+oneway interface INetworkManagementEventObserver {
/**
* Interface configuration status has changed.
*
diff --git a/core/java/android/net/INetworkStackConnector.aidl b/core/java/android/net/INetworkStackConnector.aidl
index 2df8ab7..8b64f1c 100644
--- a/core/java/android/net/INetworkStackConnector.aidl
+++ b/core/java/android/net/INetworkStackConnector.aidl
@@ -18,10 +18,12 @@
import android.net.INetworkMonitorCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IIpClientCallbacks;
/** @hide */
oneway interface INetworkStackConnector {
void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params,
in IDhcpServerCallbacks cb);
void makeNetworkMonitor(int netId, String name, in INetworkMonitorCallbacks cb);
+ void makeIpClient(in String ifName, in IIpClientCallbacks callbacks);
}
\ No newline at end of file
diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java
index 4631c56..b996cda 100644
--- a/core/java/android/net/IpPrefix.java
+++ b/core/java/android/net/IpPrefix.java
@@ -16,6 +16,8 @@
package android.net;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Pair;
@@ -83,6 +85,8 @@
* @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6).
* @hide
*/
+ @SystemApi
+ @TestApi
public IpPrefix(InetAddress address, int prefixLength) {
// We don't reuse the (byte[], int) constructor because it calls clone() on the byte array,
// which is unnecessary because getAddress() already returns a clone.
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
index a536d08..fbd602c 100644
--- a/core/java/android/net/LinkAddress.java
+++ b/core/java/android/net/LinkAddress.java
@@ -162,6 +162,8 @@
* {@link OsConstants#RT_SCOPE_LINK} or {@link OsConstants#RT_SCOPE_SITE}).
* @hide
*/
+ @SystemApi
+ @TestApi
public LinkAddress(InetAddress address, int prefixLength, int flags, int scope) {
init(address, prefixLength, flags, scope);
}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index c2963fd..6628701 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
@@ -173,7 +174,8 @@
/**
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
+ @TestApi
public LinkProperties(LinkProperties source) {
if (source != null) {
mIfaceName = source.mIfaceName;
@@ -368,7 +370,8 @@
* @return true if the DNS server was added, false if it was already present.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean addDnsServer(InetAddress dnsServer) {
if (dnsServer != null && !mDnses.contains(dnsServer)) {
mDnses.add(dnsServer);
@@ -384,7 +387,8 @@
* @return true if the DNS server was removed, false if it did not exist.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean removeDnsServer(InetAddress dnsServer) {
if (dnsServer != null) {
return mDnses.remove(dnsServer);
@@ -423,6 +427,8 @@
* @param usePrivateDns The private DNS state.
* @hide
*/
+ @TestApi
+ @SystemApi
public void setUsePrivateDns(boolean usePrivateDns) {
mUsePrivateDns = usePrivateDns;
}
@@ -448,6 +454,8 @@
* @param privateDnsServerName The private DNS server name.
* @hide
*/
+ @TestApi
+ @SystemApi
public void setPrivateDnsServerName(@Nullable String privateDnsServerName) {
mPrivateDnsServerName = privateDnsServerName;
}
@@ -510,6 +518,8 @@
* object.
* @hide
*/
+ @TestApi
+ @SystemApi
public void setValidatedPrivateDnsServers(Collection<InetAddress> dnsServers) {
mValidatedPrivateDnses.clear();
for (InetAddress dnsServer: dnsServers) {
@@ -525,6 +535,8 @@
* DNS servers on this link.
* @hide
*/
+ @TestApi
+ @SystemApi
public List<InetAddress> getValidatedPrivateDnsServers() {
return Collections.unmodifiableList(mValidatedPrivateDnses);
}
@@ -565,6 +577,8 @@
* @param addresses The {@link Collection} of PCSCF servers to set in this object.
* @hide
*/
+ @SystemApi
+ @TestApi
public void setPcscfServers(Collection<InetAddress> pcscfServers) {
mPcscfs.clear();
for (InetAddress pcscfServer: pcscfServers) {
@@ -579,6 +593,8 @@
* this link.
* @hide
*/
+ @SystemApi
+ @TestApi
public List<InetAddress> getPcscfServers() {
return Collections.unmodifiableList(mPcscfs);
}
@@ -636,7 +652,8 @@
*
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public void setTcpBufferSizes(String tcpBufferSizes) {
mTcpBufferSizes = tcpBufferSizes;
}
@@ -648,7 +665,8 @@
*
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public String getTcpBufferSizes() {
return mTcpBufferSizes;
}
@@ -699,7 +717,8 @@
*
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean removeRoute(RouteInfo route) {
return route != null &&
Objects.equals(mIfaceName, route.getInterface()) &&
@@ -767,6 +786,8 @@
* @return the NAT64 prefix.
* @hide
*/
+ @SystemApi
+ @TestApi
public @Nullable IpPrefix getNat64Prefix() {
return mNat64Prefix;
}
@@ -780,6 +801,8 @@
* @param prefix the NAT64 prefix.
* @hide
*/
+ @SystemApi
+ @TestApi
public void setNat64Prefix(IpPrefix prefix) {
if (prefix != null && prefix.getPrefixLength() != 96) {
throw new IllegalArgumentException("Only 96-bit prefixes are supported: " + prefix);
@@ -960,7 +983,8 @@
* @return {@code true} if there is an IPv4 address, {@code false} otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean hasIPv4Address() {
for (LinkAddress address : mLinkAddresses) {
if (address.getAddress() instanceof Inet4Address) {
@@ -988,7 +1012,8 @@
* @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean hasGlobalIPv6Address() {
for (LinkAddress address : mLinkAddresses) {
if (address.getAddress() instanceof Inet6Address && address.isGlobalPreferred()) {
@@ -1020,7 +1045,8 @@
* @return {@code true} if there is an IPv6 default route, {@code false} otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean hasIPv6DefaultRoute() {
for (RouteInfo r : mRoutes) {
if (r.isIPv6Default()) {
@@ -1099,6 +1125,8 @@
* @return {@code true} if the link is provisioned, {@code false} otherwise.
* @hide
*/
+ @TestApi
+ @SystemApi
public boolean isIPv4Provisioned() {
return (hasIPv4Address() &&
hasIPv4DefaultRoute() &&
@@ -1112,7 +1140,8 @@
* @return {@code true} if the link is provisioned, {@code false} otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean isIPv6Provisioned() {
return (hasGlobalIPv6Address() &&
hasIPv6DefaultRoute() &&
@@ -1126,7 +1155,8 @@
* @return {@code true} if the link is provisioned, {@code false} otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean isProvisioned() {
return (isIPv4Provisioned() || isIPv6Provisioned());
}
@@ -1138,7 +1168,8 @@
* {@code false} otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean isReachable(InetAddress ip) {
final List<RouteInfo> allRoutes = getAllRoutes();
// If we don't have a route to this IP address, it's not reachable.
diff --git a/core/java/android/net/LinkPropertiesParcelable.aidl b/core/java/android/net/LinkPropertiesParcelable.aidl
index b153dc7..6b52239 100644
--- a/core/java/android/net/LinkPropertiesParcelable.aidl
+++ b/core/java/android/net/LinkPropertiesParcelable.aidl
@@ -35,5 +35,4 @@
int mtu;
String tcpBufferSizes;
IpPrefixParcelable nat64Prefix;
- LinkPropertiesParcelable[] stackedLinks;
}
\ No newline at end of file
diff --git a/core/java/android/net/NattSocketKeepalive.java b/core/java/android/net/NattSocketKeepalive.java
new file mode 100644
index 0000000..88631ae
--- /dev/null
+++ b/core/java/android/net/NattSocketKeepalive.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.util.concurrent.Executor;
+
+/** @hide */
+public final class NattSocketKeepalive extends SocketKeepalive {
+ /** The NAT-T destination port for IPsec */
+ public static final int NATT_PORT = 4500;
+
+ @NonNull private final InetAddress mSource;
+ @NonNull private final InetAddress mDestination;
+ @NonNull private final FileDescriptor mFd;
+ private final int mResourceId;
+
+ NattSocketKeepalive(@NonNull IConnectivityManager service,
+ @NonNull Network network,
+ @NonNull FileDescriptor fd,
+ int resourceId,
+ @NonNull InetAddress source,
+ @NonNull InetAddress destination,
+ @NonNull Executor executor,
+ @NonNull Callback callback) {
+ super(service, network, executor, callback);
+ mSource = source;
+ mDestination = destination;
+ mFd = fd;
+ mResourceId = resourceId;
+ }
+
+ @Override
+ void startImpl(int intervalSec) {
+ try {
+ mService.startNattKeepaliveWithFd(mNetwork, mFd, mResourceId, intervalSec, mMessenger,
+ new Binder(), mSource.getHostAddress(), mDestination.getHostAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error starting packet keepalive: ", e);
+ stopLooper();
+ }
+ }
+
+ @Override
+ void stopImpl() {
+ try {
+ if (mSlot != null) {
+ mService.stopKeepalive(mNetwork, mSlot);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error stopping packet keepalive: ", e);
+ stopLooper();
+ }
+ }
+}
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 1b44c92..7e9bda1 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -712,6 +712,7 @@
* @hide
*/
@TestApi
+ @SystemApi
public @Transport int[] getTransportTypes() {
return BitUtils.unpackBits(mTransportTypes);
}
@@ -1312,6 +1313,8 @@
*
* @hide
*/
+ @TestApi
+ @SystemApi
public boolean satisfiedByNetworkCapabilities(NetworkCapabilities nc) {
return satisfiedByNetworkCapabilities(nc, false);
}
diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java
index af043ee..d277034 100644
--- a/core/java/android/net/NetworkStack.java
+++ b/core/java/android/net/NetworkStack.java
@@ -27,6 +27,7 @@
import android.content.ServiceConnection;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IIpClientCallbacks;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
@@ -84,6 +85,21 @@
}
/**
+ * Create an IpClient on the specified interface.
+ *
+ * <p>The IpClient will be returned asynchronously through the provided callbacks.
+ */
+ public void makeIpClient(String ifName, IIpClientCallbacks cb) {
+ requestConnector(connector -> {
+ try {
+ connector.makeIpClient(ifName, cb);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ /**
* Create a NetworkMonitor.
*
* <p>The INetworkMonitor will be returned asynchronously through the provided callbacks.
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 4eab49c..c996d01 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -39,6 +39,8 @@
import java.util.Locale;
import java.util.TreeSet;
+import android.system.ErrnoException;
+
/**
* Native methods for managing network interfaces.
*
@@ -138,6 +140,32 @@
public native static boolean queryUserAccess(int uid, int netId);
/**
+ * DNS resolver series jni method.
+ * Issue the query {@code msg} on the network designated by {@code netId}.
+ * {@code flags} is an additional config to control actual querying behavior.
+ * @return a file descriptor to watch for read events
+ */
+ public static native FileDescriptor resNetworkSend(
+ int netId, byte[] msg, int msglen, int flags) throws ErrnoException;
+
+ /**
+ * DNS resolver series jni method.
+ * Look up the {@code nsClass} {@code nsType} Resource Record (RR) associated
+ * with Domain Name {@code dname} on the network designated by {@code netId}.
+ * {@code flags} is an additional config to control actual querying behavior.
+ * @return a file descriptor to watch for read events
+ */
+ public static native FileDescriptor resNetworkQuery(
+ int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException;
+
+ /**
+ * DNS resolver series jni method.
+ * Read a result for the query associated with the {@code fd}.
+ * @return a byte array containing blob answer
+ */
+ public static native byte[] resNetworkResult(FileDescriptor fd) throws ErrnoException;
+
+ /**
* Add an entry into the ARP cache.
*/
public static void addArpEntry(Inet4Address ipv4Addr, MacAddress ethAddr, String ifname,
diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java
index e926fda..ef2269a 100644
--- a/core/java/android/net/ProxyInfo.java
+++ b/core/java/android/net/ProxyInfo.java
@@ -39,12 +39,12 @@
*/
public class ProxyInfo implements Parcelable {
- private String mHost;
- private int mPort;
- private String mExclusionList;
- private String[] mParsedExclusionList;
+ private final String mHost;
+ private final int mPort;
+ private final String mExclusionList;
+ private final String[] mParsedExclusionList;
+ private final Uri mPacFileUrl;
- private Uri mPacFileUrl;
/**
*@hide
*/
@@ -96,7 +96,8 @@
public ProxyInfo(String host, int port, String exclList) {
mHost = host;
mPort = port;
- setExclusionList(exclList);
+ mExclusionList = exclList;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
mPacFileUrl = Uri.EMPTY;
}
@@ -107,7 +108,8 @@
public ProxyInfo(Uri pacFileUrl) {
mHost = LOCAL_HOST;
mPort = LOCAL_PORT;
- setExclusionList(LOCAL_EXCL_LIST);
+ mExclusionList = LOCAL_EXCL_LIST;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
if (pacFileUrl == null) {
throw new NullPointerException();
}
@@ -121,7 +123,8 @@
public ProxyInfo(String pacFileUrl) {
mHost = LOCAL_HOST;
mPort = LOCAL_PORT;
- setExclusionList(LOCAL_EXCL_LIST);
+ mExclusionList = LOCAL_EXCL_LIST;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
mPacFileUrl = Uri.parse(pacFileUrl);
}
@@ -132,13 +135,22 @@
public ProxyInfo(Uri pacFileUrl, int localProxyPort) {
mHost = LOCAL_HOST;
mPort = localProxyPort;
- setExclusionList(LOCAL_EXCL_LIST);
+ mExclusionList = LOCAL_EXCL_LIST;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
if (pacFileUrl == null) {
throw new NullPointerException();
}
mPacFileUrl = pacFileUrl;
}
+ private static String[] parseExclusionList(String exclusionList) {
+ if (exclusionList == null) {
+ return new String[0];
+ } else {
+ return exclusionList.toLowerCase(Locale.ROOT).split(",");
+ }
+ }
+
private ProxyInfo(String host, int port, String exclList, String[] parsedExclList) {
mHost = host;
mPort = port;
@@ -159,6 +171,10 @@
mExclusionList = source.getExclusionListAsString();
mParsedExclusionList = source.mParsedExclusionList;
} else {
+ mHost = null;
+ mPort = 0;
+ mExclusionList = null;
+ mParsedExclusionList = null;
mPacFileUrl = Uri.EMPTY;
}
}
@@ -214,24 +230,14 @@
return mExclusionList;
}
- // comma separated
- private void setExclusionList(String exclusionList) {
- mExclusionList = exclusionList;
- if (mExclusionList == null) {
- mParsedExclusionList = new String[0];
- } else {
- mParsedExclusionList = exclusionList.toLowerCase(Locale.ROOT).split(",");
- }
- }
-
/**
* @hide
*/
public boolean isValid() {
if (!Uri.EMPTY.equals(mPacFileUrl)) return true;
return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost,
- mPort == 0 ? "" : Integer.toString(mPort),
- mExclusionList == null ? "" : mExclusionList);
+ mPort == 0 ? "" : Integer.toString(mPort),
+ mExclusionList == null ? "" : mExclusionList);
}
/**
@@ -262,7 +268,7 @@
sb.append("] ");
sb.append(Integer.toString(mPort));
if (mExclusionList != null) {
- sb.append(" xl=").append(mExclusionList);
+ sb.append(" xl=").append(mExclusionList);
}
} else {
sb.append("[ProxyProperties.mHost == null]");
@@ -308,8 +314,8 @@
*/
public int hashCode() {
return ((null == mHost) ? 0 : mHost.hashCode())
- + ((null == mExclusionList) ? 0 : mExclusionList.hashCode())
- + mPort;
+ + ((null == mExclusionList) ? 0 : mExclusionList.hashCode())
+ + mPort;
}
/**
@@ -352,8 +358,7 @@
}
String exclList = in.readString();
String[] parsedExclList = in.readStringArray();
- ProxyInfo proxyProperties =
- new ProxyInfo(host, port, exclList, parsedExclList);
+ ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList);
return proxyProperties;
}
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index 6bf2c67..5c0f758 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -110,6 +110,8 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
public RouteInfo(IpPrefix destination, InetAddress gateway, String iface, int type) {
switch (type) {
case RTN_UNICAST:
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index abc1cac..90dccb5 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -86,7 +86,16 @@
* <p>On development devices, "setprop socket.relaxsslcheck yes" bypasses all
* SSL certificate and hostname checks for testing purposes. This setting
* requires root access.
+ *
+ * @deprecated This class has less error-prone replacements using standard APIs. To create an
+ * {@code SSLSocket}, obtain an {@link SSLSocketFactory} from {@link SSLSocketFactory#getDefault()}
+ * or {@link javax.net.ssl.SSLContext#getSocketFactory()}. To verify hostnames, pass
+ * {@code "HTTPS"} to
+ * {@link javax.net.ssl.SSLParameters#setEndpointIdentificationAlgorithm(String)}. To enable ALPN,
+ * use {@link javax.net.ssl.SSLParameters#setApplicationProtocols(String[])}. To enable SNI,
+ * use {@link javax.net.ssl.SSLParameters#setServerNames(java.util.List)}.
*/
+@Deprecated
public class SSLCertificateSocketFactory extends SSLSocketFactory {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private static final String TAG = "SSLCertificateSocketFactory";
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
new file mode 100644
index 0000000..97d50f4
--- /dev/null
+++ b/core/java/android/net/SocketKeepalive.java
@@ -0,0 +1,224 @@
+/*
+ * 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.net;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Process;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Allows applications to request that the system periodically send specific packets on their
+ * behalf, using hardware offload to save battery power.
+ *
+ * To request that the system send keepalives, call one of the methods that return a
+ * {@link SocketKeepalive} object, such as {@link ConnectivityManager#createSocketKeepalive},
+ * passing in a non-null callback. If the {@link SocketKeepalive} is successfully
+ * started, the callback's {@code onStarted} method will be called. If an error occurs,
+ * {@code onError} will be called, specifying one of the {@code ERROR_*} constants in this
+ * class.
+ *
+ * To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call
+ * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or
+ * {@link SocketKeepalive.Callback#onError} if an error occurred.
+ */
+public abstract class SocketKeepalive implements AutoCloseable {
+ static final String TAG = "SocketKeepalive";
+
+ /** @hide */
+ public static final int SUCCESS = 0;
+
+ /** @hide */
+ public static final int NO_KEEPALIVE = -1;
+
+ /** @hide */
+ public static final int DATA_RECEIVED = -2;
+
+ /** @hide */
+ public static final int BINDER_DIED = -10;
+
+ /** The specified {@code Network} is not connected. */
+ public static final int ERROR_INVALID_NETWORK = -20;
+ /** The specified IP addresses are invalid. For example, the specified source IP address is
+ * not configured on the specified {@code Network}. */
+ public static final int ERROR_INVALID_IP_ADDRESS = -21;
+ /** The requested port is invalid. */
+ public static final int ERROR_INVALID_PORT = -22;
+ /** The packet length is invalid (e.g., too long). */
+ public static final int ERROR_INVALID_LENGTH = -23;
+ /** The packet transmission interval is invalid (e.g., too short). */
+ public static final int ERROR_INVALID_INTERVAL = -24;
+ /** The target socket is invalid. */
+ public static final int ERROR_INVALID_SOCKET = -25;
+ /** The target socket is not idle. */
+ public static final int ERROR_SOCKET_NOT_IDLE = -26;
+
+ /** The hardware does not support this request. */
+ public static final int ERROR_HARDWARE_UNSUPPORTED = -30;
+ /** The hardware returned an error. */
+ public static final int ERROR_HARDWARE_ERROR = -31;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "ERROR_" }, value = {
+ ERROR_INVALID_NETWORK,
+ ERROR_INVALID_IP_ADDRESS,
+ ERROR_INVALID_PORT,
+ ERROR_INVALID_LENGTH,
+ ERROR_INVALID_INTERVAL,
+ ERROR_INVALID_SOCKET,
+ ERROR_SOCKET_NOT_IDLE
+ })
+ public @interface ErrorCode {}
+
+ /**
+ * The minimum interval in seconds between keepalive packet transmissions.
+ *
+ * @hide
+ **/
+ public static final int MIN_INTERVAL_SEC = 10;
+
+ /**
+ * The maximum interval in seconds between keepalive packet transmissions.
+ *
+ * @hide
+ **/
+ public static final int MAX_INTERVAL_SEC = 3600;
+
+ @NonNull final IConnectivityManager mService;
+ @NonNull final Network mNetwork;
+ @NonNull private final Executor mExecutor;
+ @NonNull private final SocketKeepalive.Callback mCallback;
+ @NonNull private final Looper mLooper;
+ @NonNull final Messenger mMessenger;
+ @NonNull Integer mSlot;
+
+ SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
+ @NonNull Executor executor, @NonNull Callback callback) {
+ mService = service;
+ mNetwork = network;
+ mExecutor = executor;
+ mCallback = callback;
+ // TODO: 1. Use other thread modeling instead of create one thread for every instance to
+ // reduce the memory cost.
+ // 2. support restart.
+ // 3. Fix race condition which caused by rapidly start and stop.
+ HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND
+ + Process.THREAD_PRIORITY_LESS_FAVORABLE);
+ thread.start();
+ mLooper = thread.getLooper();
+ mMessenger = new Messenger(new Handler(mLooper) {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case NetworkAgent.EVENT_PACKET_KEEPALIVE:
+ final int status = message.arg2;
+ try {
+ if (status == SUCCESS) {
+ if (mSlot == null) {
+ mSlot = message.arg1;
+ mExecutor.execute(() -> mCallback.onStarted());
+ } else {
+ mSlot = null;
+ stopLooper();
+ mExecutor.execute(() -> mCallback.onStopped());
+ }
+ } else if (status == DATA_RECEIVED) {
+ stopLooper();
+ mExecutor.execute(() -> mCallback.onDataReceived());
+ } else {
+ stopLooper();
+ mExecutor.execute(() -> mCallback.onError(status));
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in keepalive callback(" + status + ")", e);
+ }
+ break;
+ default:
+ Log.e(TAG, "Unhandled message " + Integer.toHexString(message.what));
+ break;
+ }
+ }
+ });
+ }
+
+ /**
+ * Request that keepalive be started with the given {@code intervalSec}. See
+ * {@link SocketKeepalive}.
+ *
+ * @param intervalSec The target interval in seconds between keepalive packet transmissions.
+ * The interval should be between 10 seconds and 3600 seconds, otherwise
+ * {@link #ERROR_INVALID_INTERVAL} will be returned.
+ */
+ public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC)
+ int intervalSec) {
+ startImpl(intervalSec);
+ }
+
+ abstract void startImpl(int intervalSec);
+
+ /** @hide */
+ protected void stopLooper() {
+ // TODO: remove this after changing thread modeling.
+ mLooper.quit();
+ }
+
+ /**
+ * Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped}
+ * before using the object. See {@link SocketKeepalive}.
+ */
+ public final void stop() {
+ stopImpl();
+ }
+
+ abstract void stopImpl();
+
+ /**
+ * Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be
+ * usable again if {@code close()} is called.
+ */
+ @Override
+ public final void close() {
+ stop();
+ stopLooper();
+ }
+
+ /**
+ * The callback which app can use to learn the status changes of {@link SocketKeepalive}. See
+ * {@link SocketKeepalive}.
+ */
+ public static class Callback {
+ /** The requested keepalive was successfully started. */
+ public void onStarted() {}
+ /** The keepalive was successfully stopped. */
+ public void onStopped() {}
+ /** An error occurred. */
+ public void onError(@ErrorCode int error) {}
+ /** The keepalive on a TCP socket was stopped because the socket received data. */
+ public void onDataReceived() {}
+ }
+}
diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java
index f0c0462..dc099a4 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -368,6 +368,29 @@
}
/**
+ * Returns whether the service is running in always-on VPN mode.
+ */
+ public final boolean isAlwaysOn() {
+ try {
+ return getService().isCallerCurrentAlwaysOnVpnApp();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether the service is running in always-on VPN mode blocking connections without
+ * VPN.
+ */
+ public final boolean isLockdownEnabled() {
+ try {
+ return getService().isCallerCurrentAlwaysOnVpnLockdownApp();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return the communication interface to the service. This method returns
* {@code null} on {@link Intent}s other than {@link #SERVICE_INTERFACE}
* action. Applications overriding this method must identify the intent
@@ -486,6 +509,15 @@
}
/**
+ * Sets an HTTP proxy for the VPN network. This proxy is only a recommendation
+ * and it is possible that some apps will ignore it.
+ */
+ public Builder setHttpProxy(ProxyInfo proxyInfo) {
+ mConfig.proxyInfo = proxyInfo;
+ return this;
+ }
+
+ /**
* Add a network address to the VPN interface. Both IPv4 and IPv6
* addresses are supported. At least one address must be set before
* calling {@link #establish}.
diff --git a/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
index 57f59a1..fb4ca3b 100644
--- a/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
+++ b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
@@ -25,6 +25,6 @@
* Network attributes were fetched for the specified L2 key. While the L2 key will never
* be null, the attributes may be if no data is stored about this L2 key.
*/
- void onL2KeyResponse(in StatusParcelable status, in String l2Key,
+ void onNetworkAttributesRetrieved(in StatusParcelable status, in String l2Key,
in NetworkAttributesParcelable attributes);
}
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java
index b932d21..6a9eae0 100644
--- a/core/java/android/net/ipmemorystore/NetworkAttributes.java
+++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -37,27 +37,57 @@
public class NetworkAttributes {
private static final boolean DBG = true;
+ // Weight cutoff for grouping. To group, a similarity score is computed with the following
+ // algorithm : if both fields are non-null and equals() then add their assigned weight, else if
+ // both are null then add a portion of their assigned weight (see NULL_MATCH_WEIGHT),
+ // otherwise add nothing.
+ // As a guideline, this should be something like 60~75% of the total weights in this class. The
+ // design states "in essence a reader should imagine that if two important columns don't match,
+ // or one important and several unimportant columns don't match then the two records are
+ // considered a different group".
+ private static final float TOTAL_WEIGHT_CUTOFF = 520.0f;
+ // The portion of the weight that is earned when scoring group-sameness by having both columns
+ // being null. This is because some networks rightfully don't have some attributes (e.g. a
+ // V6-only network won't have an assigned V4 address) and both being null should count for
+ // something, but attributes may also be null just because data is unavailable.
+ private static final float NULL_MATCH_WEIGHT = 0.25f;
+
// The v4 address that was assigned to this device the last time it joined this network.
// This typically comes from DHCP but could be something else like static configuration.
// This does not apply to IPv6.
// TODO : add a list of v6 prefixes for the v6 case.
@Nullable
public final Inet4Address assignedV4Address;
+ private static final float WEIGHT_ASSIGNEDV4ADDR = 300.0f;
// Optionally supplied by the client if it has an opinion on L3 network. For example, this
// could be a hash of the SSID + security type on WiFi.
@Nullable
public final String groupHint;
+ private static final float WEIGHT_GROUPHINT = 300.0f;
// The list of DNS server addresses.
@Nullable
public final List<InetAddress> dnsAddresses;
+ private static final float WEIGHT_DNSADDRESSES = 200.0f;
// The mtu on this network.
@Nullable
public final Integer mtu;
+ private static final float WEIGHT_MTU = 50.0f;
- NetworkAttributes(
+ // The sum of all weights in this class. Tests ensure that this stays equal to the total of
+ // all weights.
+ /** @hide */
+ @VisibleForTesting
+ public static final float TOTAL_WEIGHT = WEIGHT_ASSIGNEDV4ADDR
+ + WEIGHT_GROUPHINT
+ + WEIGHT_DNSADDRESSES
+ + WEIGHT_MTU;
+
+ /** @hide */
+ @VisibleForTesting
+ public NetworkAttributes(
@Nullable final Inet4Address assignedV4Address,
@Nullable final String groupHint,
@Nullable final List<InetAddress> dnsAddresses,
@@ -126,6 +156,34 @@
return parcelable;
}
+ private float samenessContribution(final float weight,
+ @Nullable final Object o1, @Nullable final Object o2) {
+ if (null == o1) {
+ return (null == o2) ? weight * NULL_MATCH_WEIGHT : 0f;
+ }
+ return Objects.equals(o1, o2) ? weight : 0f;
+ }
+
+ /** @hide */
+ public float getNetworkGroupSamenessConfidence(@NonNull final NetworkAttributes o) {
+ final float samenessScore =
+ samenessContribution(WEIGHT_ASSIGNEDV4ADDR, assignedV4Address, o.assignedV4Address)
+ + samenessContribution(WEIGHT_GROUPHINT, groupHint, o.groupHint)
+ + samenessContribution(WEIGHT_DNSADDRESSES, dnsAddresses, o.dnsAddresses)
+ + samenessContribution(WEIGHT_MTU, mtu, o.mtu);
+ // The minimum is 0, the max is TOTAL_WEIGHT and should be represented by 1.0, and
+ // TOTAL_WEIGHT_CUTOFF should represent 0.5, but there is no requirement that
+ // TOTAL_WEIGHT_CUTOFF would be half of TOTAL_WEIGHT (indeed, it should not be).
+ // So scale scores under the cutoff between 0 and 0.5, and the scores over the cutoff
+ // between 0.5 and 1.0.
+ if (samenessScore < TOTAL_WEIGHT_CUTOFF) {
+ return samenessScore / (TOTAL_WEIGHT_CUTOFF * 2);
+ } else {
+ return (samenessScore - TOTAL_WEIGHT_CUTOFF) / (TOTAL_WEIGHT - TOTAL_WEIGHT_CUTOFF) / 2
+ + 0.5f;
+ }
+ }
+
/** @hide */
public static class Builder {
@Nullable
@@ -194,6 +252,12 @@
}
}
+ /** @hide */
+ public boolean isEmpty() {
+ return (null == assignedV4Address) && (null == groupHint)
+ && (null == dnsAddresses) && (null == mtu);
+ }
+
@Override
public boolean equals(@Nullable final Object o) {
if (!(o instanceof NetworkAttributes)) return false;
diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
index d040dcc..291aca8 100644
--- a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
+++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
@@ -91,7 +91,8 @@
return confidence > 0.5 ? NETWORK_SAME : NETWORK_DIFFERENT;
}
- SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2,
+ /** @hide */
+ public SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2,
final float confidence) {
this.l2Key1 = l2Key1;
this.l2Key2 = l2Key2;
diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java
index c04fcdc..5b5a235 100644
--- a/core/java/android/net/metrics/IpConnectivityLog.java
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -18,9 +18,9 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics;
+import android.net.Network;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -50,7 +50,8 @@
public interface Event extends Parcelable {}
/** @hide */
- @UnsupportedAppUsage
+ @SystemApi
+ @TestApi
public IpConnectivityLog() {
}
@@ -128,6 +129,18 @@
/**
* Log an IpConnectivity event.
+ * @param network the network associated with the event.
+ * @param transports the current transports of the network associated with the event, as defined
+ * in NetworkCapabilities.
+ * @param data is a Parcelable instance representing the event.
+ * @return true if the event was successfully logged.
+ */
+ public boolean log(Network network, int[] transports, Event data) {
+ return log(network.netId, transports, data);
+ }
+
+ /**
+ * Log an IpConnectivity event.
* @param netid the id of the network associated with the event.
* @param transports the current transports of the network associated with the event, as defined
* in NetworkCapabilities.
diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java
index d308246..04a2e6e 100644
--- a/core/java/android/net/metrics/RaEvent.java
+++ b/core/java/android/net/metrics/RaEvent.java
@@ -16,7 +16,8 @@
package android.net.metrics;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -24,19 +25,28 @@
* An event logged when the APF packet socket receives an RA packet.
* {@hide}
*/
+@SystemApi
+@TestApi
public final class RaEvent implements IpConnectivityLog.Event {
- public static final long NO_LIFETIME = -1L;
+ private static final long NO_LIFETIME = -1L;
// Lifetime in seconds of options found in a single RA packet.
// When an option is not set, the value of the associated field is -1;
+ /** @hide */
public final long routerLifetime;
+ /** @hide */
public final long prefixValidLifetime;
+ /** @hide */
public final long prefixPreferredLifetime;
+ /** @hide */
public final long routeInfoLifetime;
+ /** @hide */
public final long rdnssLifetime;
+ /** @hide */
public final long dnsslLifetime;
+ /** @hide */
public RaEvent(long routerLifetime, long prefixValidLifetime, long prefixPreferredLifetime,
long routeInfoLifetime, long rdnssLifetime, long dnsslLifetime) {
this.routerLifetime = routerLifetime;
@@ -47,6 +57,7 @@
this.dnsslLifetime = dnsslLifetime;
}
+ /** @hide */
private RaEvent(Parcel in) {
routerLifetime = in.readLong();
prefixValidLifetime = in.readLong();
@@ -56,6 +67,7 @@
dnsslLifetime = in.readLong();
}
+ /** @hide */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeLong(routerLifetime);
@@ -66,6 +78,7 @@
out.writeLong(dnsslLifetime);
}
+ /** @hide */
@Override
public int describeContents() {
return 0;
@@ -83,6 +96,7 @@
.toString();
}
+ /** @hide */
public static final Parcelable.Creator<RaEvent> CREATOR = new Parcelable.Creator<RaEvent>() {
public RaEvent createFromParcel(Parcel in) {
return new RaEvent(in);
@@ -102,47 +116,39 @@
long rdnssLifetime = NO_LIFETIME;
long dnsslLifetime = NO_LIFETIME;
- @UnsupportedAppUsage
public Builder() {
}
- @UnsupportedAppUsage
public RaEvent build() {
return new RaEvent(routerLifetime, prefixValidLifetime, prefixPreferredLifetime,
routeInfoLifetime, rdnssLifetime, dnsslLifetime);
}
- @UnsupportedAppUsage
public Builder updateRouterLifetime(long lifetime) {
routerLifetime = updateLifetime(routerLifetime, lifetime);
return this;
}
- @UnsupportedAppUsage
public Builder updatePrefixValidLifetime(long lifetime) {
prefixValidLifetime = updateLifetime(prefixValidLifetime, lifetime);
return this;
}
- @UnsupportedAppUsage
public Builder updatePrefixPreferredLifetime(long lifetime) {
prefixPreferredLifetime = updateLifetime(prefixPreferredLifetime, lifetime);
return this;
}
- @UnsupportedAppUsage
public Builder updateRouteInfoLifetime(long lifetime) {
routeInfoLifetime = updateLifetime(routeInfoLifetime, lifetime);
return this;
}
- @UnsupportedAppUsage
public Builder updateRdnssLifetime(long lifetime) {
rdnssLifetime = updateLifetime(rdnssLifetime, lifetime);
return this;
}
- @UnsupportedAppUsage
public Builder updateDnsslLifetime(long lifetime) {
dnsslLifetime = updateLifetime(dnsslLifetime, lifetime);
return this;
diff --git a/services/net/java/android/net/util/MultinetworkPolicyTracker.java b/core/java/android/net/util/MultinetworkPolicyTracker.java
similarity index 100%
rename from services/net/java/android/net/util/MultinetworkPolicyTracker.java
rename to core/java/android/net/util/MultinetworkPolicyTracker.java
diff --git a/core/java/android/net/util/SocketUtils.java b/core/java/android/net/util/SocketUtils.java
new file mode 100644
index 0000000..de67cf5
--- /dev/null
+++ b/core/java/android/net/util/SocketUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 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.net.util;
+
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_BINDTODEVICE;
+
+import android.annotation.SystemApi;
+import android.net.NetworkUtils;
+import android.system.ErrnoException;
+import android.system.NetlinkSocketAddress;
+import android.system.Os;
+import android.system.PacketSocketAddress;
+
+import java.io.FileDescriptor;
+import java.net.SocketAddress;
+
+/**
+ * Collection of utilities to interact with raw sockets.
+ * @hide
+ */
+@SystemApi
+public class SocketUtils {
+ /**
+ * Create a raw datagram socket that is bound to an interface.
+ *
+ * <p>Data sent through the socket will go directly to the underlying network, ignoring VPNs.
+ */
+ public static void bindSocketToInterface(FileDescriptor socket, String iface)
+ throws ErrnoException {
+ // SO_BINDTODEVICE actually takes a string. This works because the first member
+ // of struct ifreq is a NULL-terminated interface name.
+ // TODO: add a setsockoptString()
+ Os.setsockoptIfreq(socket, SOL_SOCKET, SO_BINDTODEVICE, iface);
+ NetworkUtils.protectFromVpn(socket);
+ }
+
+ /**
+ * Make a socket address to communicate with netlink.
+ */
+ public static SocketAddress makeNetlinkSocketAddress(int portId, int groupsMask) {
+ return new NetlinkSocketAddress(portId, groupsMask);
+ }
+
+ /**
+ * Make a socket address to bind to packet sockets.
+ */
+ public static SocketAddress makePacketSocketAddress(short protocol, int ifIndex) {
+ return new PacketSocketAddress(protocol, ifIndex);
+ }
+
+ /**
+ * Make a socket address to send raw packets.
+ */
+ public static SocketAddress makePacketSocketAddress(int ifIndex, byte[] hwAddr) {
+ return new PacketSocketAddress(ifIndex, hwAddr);
+ }
+
+ private SocketUtils() {}
+}
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
index 950f381..6daa5b4 100644
--- a/core/java/android/os/AppZygote.java
+++ b/core/java/android/os/AppZygote.java
@@ -103,7 +103,7 @@
String abi = mAppInfo.primaryCpuAbi != null ? mAppInfo.primaryCpuAbi :
Build.SUPPORTED_ABIS[0];
try {
- mZygote = Process.zygoteProcess.startChildZygote(
+ mZygote = Process.ZYGOTE_PROCESS.startChildZygote(
"com.android.internal.os.AppZygoteInit",
mAppInfo.processName + "_zygote",
mZygoteUid,
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index c5a51f1..518528d 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -79,23 +79,27 @@
* Called when taking bugreport resulted in an error.
*
* @param errorCode the error that occurred. Possible values are
- * {@code BUGREPORT_ERROR_INVALID_INPUT}, {@code BUGREPORT_ERROR_RUNTIME}.
+ * {@code BUGREPORT_ERROR_INVALID_INPUT},
+ * {@code BUGREPORT_ERROR_RUNTIME},
+ * {@code BUGREPORT_ERROR_USER_DENIED_CONSENT}.
*/
void onError(@BugreportErrorCode int errorCode);
/**
- * Called when taking bugreport finishes successfully
- *
- * @param durationMs time capturing bugreport took in milliseconds
- * @param title title for the bugreport; helpful in reminding the user why they took it
- * @param description detailed description for the bugreport
+ * Called when taking bugreport finishes successfully.
*/
- void onFinished(long durationMs, @NonNull String title,
- @NonNull String description);
+ void onFinished();
}
/**
- * Starts a bugreport asynchronously.
+ * Starts a bugreport.
+ *
+ * <p>This starts a bugreport in the background. However the call itself can take several
+ * seconds to return in the worst case. {@code listener} will receive progress and status
+ * updates.
+ *
+ * <p>The bugreport artifacts will be copied over to the given file descriptors only if the
+ * user consents to sharing with the calling app.
*
* @param bugreportFd file to write the bugreport. This should be opened in write-only,
* append mode.
@@ -107,7 +111,7 @@
@RequiresPermission(android.Manifest.permission.DUMP)
public void startBugreport(@NonNull FileDescriptor bugreportFd,
@Nullable FileDescriptor screenshotFd,
- @NonNull BugreportParams params, @Nullable BugreportListener listener) {
+ @NonNull BugreportParams params, @NonNull BugreportListener listener) {
// TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary.
DumpstateListener dsListener = new DumpstateListener(listener);
@@ -121,6 +125,18 @@
}
}
+ /*
+ * Cancels a currently running bugreport.
+ */
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public void cancelBugreport() {
+ try {
+ mBinder.cancelBugreport();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private final class DumpstateListener extends IDumpstateListener.Stub
implements DeathRecipient {
private final BugreportListener mListener;
@@ -145,9 +161,13 @@
}
@Override
- public void onFinished(long durationMs, String title, String description)
- throws RemoteException {
- mListener.onFinished(durationMs, title, description);
+ public void onFinished() throws RemoteException {
+ try {
+ mListener.onFinished();
+ } finally {
+ // The bugreport has finished. Let's shutdown the service to minimize its footprint.
+ cancelBugreport();
+ }
}
// Old methods; should go away
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index ca39051..51c3c4c 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -72,6 +72,7 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Comparator;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -831,6 +832,16 @@
return false;
}
+ /** {@hide} */
+ public static boolean contains(Collection<File> dirs, File file) {
+ for (File dir : dirs) {
+ if (contains(dir, file)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Test if a file lives under the given directory, either as a direct child
* or a distant grandchild.
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index f51ba9a..5bf9095 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -23,10 +23,15 @@
import android.content.pm.ResolveInfo;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
+import android.gamedriver.GameDriverProto.Blacklist;
+import android.gamedriver.GameDriverProto.Blacklists;
import android.opengl.EGL14;
import android.provider.Settings;
+import android.util.Base64;
import android.util.Log;
+import com.android.framework.protobuf.InvalidProtocolBufferException;
+
import dalvik.system.VMRuntime;
import java.io.BufferedReader;
@@ -62,6 +67,8 @@
private static final String ANGLE_RULES_FILE = "a4a_rules.json";
private static final String ANGLE_TEMP_RULES = "debug.angle.rules";
private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID";
+ private static final String GAME_DRIVER_BLACKLIST_FLAG = "blacklist";
+ private static final int BASE64_FLAGS = Base64.NO_PADDING | Base64.NO_WRAP;
private ClassLoader mClassLoader;
private String mLayerPath;
@@ -480,6 +487,24 @@
return;
}
+ ApplicationInfo driverInfo;
+ try {
+ driverInfo = context.getPackageManager().getApplicationInfo(driverPackageName,
+ PackageManager.MATCH_SYSTEM_ONLY);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "driver package '" + driverPackageName + "' not installed");
+ return;
+ }
+
+ // O drivers are restricted to the sphal linker namespace, so don't try to use
+ // packages unless they declare they're compatible with that restriction.
+ if (driverInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+ if (DEBUG) {
+ Log.w(TAG, "updated driver package is not known to be compatible with O");
+ }
+ return;
+ }
+
// To minimize risk of driver updates crippling the device beyond user repair, never use an
// updated driver for privileged or non-updated system apps. Presumably pre-installed apps
// were tested thoroughly with the pre-installed driver.
@@ -491,7 +516,7 @@
// GUP_DEV_ALL_APPS
// 0: Default (Invalid values fallback to default as well)
- // 1: All apps use Game Update Package
+ // 1: All apps use Game Driver
// 2: All apps use system graphics driver
int gupDevAllApps = coreSettings.getInt(Settings.Global.GUP_DEV_ALL_APPS, 0);
if (gupDevAllApps == 2) {
@@ -510,33 +535,45 @@
}
return;
}
+ boolean isDevOptIn = getGlobalSettingsString(coreSettings,
+ Settings.Global.GUP_DEV_OPT_IN_APPS)
+ .contains(ai.packageName);
- if (!getGlobalSettingsString(coreSettings, Settings.Global.GUP_DEV_OPT_IN_APPS)
- .contains(ai.packageName)
- && !onWhitelist(context, driverPackageName, ai.packageName)) {
+ if (!isDevOptIn && !onWhitelist(context, driverPackageName, ai.packageName)) {
if (DEBUG) {
Log.w(TAG, ai.packageName + " is not on the whitelist.");
}
return;
}
- }
- ApplicationInfo driverInfo;
- try {
- driverInfo = context.getPackageManager().getApplicationInfo(driverPackageName,
- PackageManager.MATCH_SYSTEM_ONLY);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "driver package '" + driverPackageName + "' not installed");
- return;
- }
-
- // O drivers are restricted to the sphal linker namespace, so don't try to use
- // packages unless they declare they're compatible with that restriction.
- if (driverInfo.targetSdkVersion < Build.VERSION_CODES.O) {
- if (DEBUG) {
- Log.w(TAG, "updated driver package is not known to be compatible with O");
+ if (!isDevOptIn) {
+ // At this point, the application is on the whitelist only, check whether it's
+ // on the blacklist, terminate early when it's on the blacklist.
+ try {
+ // TODO(b/121350991) Switch to DeviceConfig with property listener.
+ String base64String = coreSettings.getString(Settings.Global.GUP_BLACKLIST);
+ if (base64String != null && !base64String.isEmpty()) {
+ Blacklists blacklistsProto = Blacklists.parseFrom(
+ Base64.decode(base64String, BASE64_FLAGS));
+ List<Blacklist> blacklists = blacklistsProto.getBlacklistsList();
+ long driverVersionCode = driverInfo.longVersionCode;
+ for (Blacklist blacklist : blacklists) {
+ if (blacklist.getVersionCode() == driverVersionCode) {
+ for (String packageName : blacklist.getPackageNamesList()) {
+ if (packageName == ai.packageName) {
+ return;
+ }
+ }
+ break;
+ }
+ }
+ }
+ } catch (InvalidProtocolBufferException e) {
+ if (DEBUG) {
+ Log.w(TAG, "Can't parse blacklist, skip and continue...");
+ }
+ }
}
- return;
}
String abi = chooseAbi(driverInfo);
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
index a4d5c6f..92fcbb6 100644
--- a/core/java/android/os/HandlerThread.java
+++ b/core/java/android/os/HandlerThread.java
@@ -20,8 +20,10 @@
import android.annotation.Nullable;
/**
- * Handy class for starting a new thread that has a looper. The looper can then be
- * used to create handler classes. Note that start() must still be called.
+ * A {@link Thread} that has a {@link Looper}.
+ * The {@link Looper} can then be used to create {@link Handler}s.
+ * <p>
+ * Note that just like with a regular {@link Thread}, {@link #start()} must still be called.
*/
public class HandlerThread extends Thread {
int mPriority;
diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl
index 8271701..fe17c6b 100644
--- a/core/java/android/os/IDeviceIdleController.aidl
+++ b/core/java/android/os/IDeviceIdleController.aidl
@@ -45,4 +45,6 @@
void exitIdle(String reason);
boolean registerMaintenanceActivityListener(IMaintenanceActivityListener listener);
void unregisterMaintenanceActivityListener(IMaintenanceActivityListener listener);
+ int setPreIdleTimeoutMode(int Mode);
+ void resetPreIdleTimeoutMode();
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 0942d97..4ce760f 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1724,6 +1724,25 @@
= "android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED";
/**
+ * Constant for PreIdleTimeout normal mode (default mode, not short nor extend timeout) .
+ * @hide
+ */
+ public static final int PRE_IDLE_TIMEOUT_MODE_NORMAL = 0;
+
+ /**
+ * Constant for PreIdleTimeout long mode (extend timeout to keep in inactive mode
+ * longer).
+ * @hide
+ */
+ public static final int PRE_IDLE_TIMEOUT_MODE_LONG = 1;
+
+ /**
+ * Constant for PreIdleTimeout short mode (short timeout to go to doze mode quickly)
+ * @hide
+ */
+ public static final int PRE_IDLE_TIMEOUT_MODE_SHORT = 2;
+
+ /**
* A wake lock is a mechanism to indicate that your application needs
* to have the device stay on.
* <p>
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 760fef7..d2ab053 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -32,23 +32,12 @@
private static final String LOG_TAG = "Process";
/**
- * @hide for internal use only.
- */
- public static final String ZYGOTE_SOCKET = "zygote";
-
- /**
- * @hide for internal use only.
- */
- public static final String SECONDARY_ZYGOTE_SOCKET = "zygote_secondary";
-
- /**
* An invalid UID value.
*/
public static final int INVALID_UID = -1;
/**
* Defines the root UID.
- * @hide
*/
public static final int ROOT_UID = 0;
@@ -64,7 +53,6 @@
/**
* Defines the UID/GID for the user shell.
- * @hide
*/
public static final int SHELL_UID = 2000;
@@ -118,7 +106,6 @@
/**
* Defines the UID/GID for the Bluetooth service process.
- * @hide
*/
public static final int BLUETOOTH_UID = 1002;
@@ -482,8 +469,7 @@
* State associated with the zygote process.
* @hide
*/
- public static final ZygoteProcess zygoteProcess =
- new ZygoteProcess(ZYGOTE_SOCKET, SECONDARY_ZYGOTE_SOCKET);
+ public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess();
/**
* Start a new process.
@@ -541,10 +527,10 @@
@Nullable String[] packagesForUid,
@Nullable String[] visibleVols,
@Nullable String[] zygoteArgs) {
- return zygoteProcess.start(processClass, niceName, uid, gid, gids,
+ return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
- packagesForUid, visibleVols, zygoteArgs);
+ packagesForUid, visibleVols, /*useBlastulaPool=*/ true, zygoteArgs);
}
/** @hide */
@@ -565,7 +551,7 @@
return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
- packagesForUid, visibleVols, zygoteArgs);
+ packagesForUid, visibleVols, /*useBlastulaPool=*/ false, zygoteArgs);
}
/**
diff --git a/core/java/android/os/ServiceSpecificException.java b/core/java/android/os/ServiceSpecificException.java
index 3e0f6da..3b0f26ae 100644
--- a/core/java/android/os/ServiceSpecificException.java
+++ b/core/java/android/os/ServiceSpecificException.java
@@ -15,6 +15,8 @@
*/
package android.os;
+import android.annotation.SystemApi;
+
/**
* An exception specific to a service.
*
@@ -28,6 +30,7 @@
*
* @hide
*/
+@SystemApi
public class ServiceSpecificException extends RuntimeException {
public final int errorCode;
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 01d85c6..99fb608 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.ContentResolver;
@@ -25,6 +26,8 @@
import android.net.Uri;
import android.util.MathUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
/**
@@ -52,26 +55,20 @@
* A click effect.
*
* @see #get(int)
- * @hide
*/
- @TestApi
public static final int EFFECT_CLICK = Effect.CLICK;
/**
* A double click effect.
*
* @see #get(int)
- * @hide
*/
- @TestApi
public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
/**
* A tick effect.
* @see #get(int)
- * @hide
*/
- @TestApi
public static final int EFFECT_TICK = Effect.TICK;
/**
@@ -93,9 +90,7 @@
/**
* A heavy click effect.
* @see #get(int)
- * @hide
*/
- @TestApi
public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
/** {@hide} */
@@ -136,6 +131,16 @@
Effect.RINGTONE_15
};
+ /** @hide */
+ @IntDef(prefix = { "EFFECT_" }, value = {
+ EFFECT_TICK,
+ EFFECT_CLICK,
+ EFFECT_HEAVY_CLICK,
+ EFFECT_DOUBLE_CLICK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EffectType {}
+
/** @hide to prevent subclassing from outside of the framework */
public VibrationEffect() { }
@@ -219,6 +224,27 @@
}
/**
+ * Create a predefined vibration effect.
+ *
+ * Predefined effects are a set of common vibration effects that should be identical, regardless
+ * of the app they come from, in order to provide a cohesive experience for users across
+ * the entire device. They also may be custom tailored to the device hardware in order to
+ * provide a better experience than you could otherwise build using the generic building
+ * blocks.
+ *
+ * This will fallback to a generic pattern if one exists and there does not exist a
+ * hardware-specific implementation of the effect.
+ *
+ * @param effectId The ID of the effect to perform:
+ * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createPrebaked(@EffectType int effectId) {
+ return get(effectId, true);
+ }
+
+ /**
* Get a predefined vibration effect.
*
* Predefined effects are a set of common vibration effects that should be identical, regardless
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index 2299ab2..76fe560 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -140,6 +140,21 @@
return mUids[index];
}
+ /**
+ * Return the UID to which this WorkSource should be attributed to, i.e, the UID that
+ * initiated the work and not the UID performing it. If the WorkSource has no UIDs, returns -1
+ * instead.
+ *
+ * @hide
+ */
+ public int getAttributionUid() {
+ if (isEmpty()) {
+ return -1;
+ }
+
+ return mNum > 0 ? mUids[0] : mChains.get(0).getAttributionUid();
+ }
+
/** @hide */
@TestApi
public String getName(int index) {
@@ -912,17 +927,18 @@
/**
* Return the UID to which this WorkChain should be attributed to, i.e, the UID that
- * initiated the work and not the UID performing it.
+ * initiated the work and not the UID performing it. Returns -1 if the chain is empty.
*/
public int getAttributionUid() {
- return mUids[0];
+ return mSize > 0 ? mUids[0] : -1;
}
/**
* Return the tag associated with the attribution UID. See (@link #getAttributionUid}.
+ * Returns null if the chain is empty.
*/
public String getAttributionTag() {
- return mTags[0];
+ return mTags.length > 0 ? mTags[0] : null;
}
// TODO: The following three trivial getters are purely for testing and will be removed
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index ec77821..9e47179 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -62,87 +62,161 @@
* {@hide}
*/
public class ZygoteProcess {
+
+ /**
+ * @hide for internal use only.
+ */
+ public static final String ZYGOTE_SOCKET_NAME = "zygote";
+
+ /**
+ * @hide for internal use only.
+ */
+ public static final String ZYGOTE_SECONDARY_SOCKET_NAME = "zygote_secondary";
+
+ /**
+ * @hide for internal use only
+ */
+ public static final String BLASTULA_POOL_SOCKET_NAME = "blastula_pool";
+
+ /**
+ * @hide for internal use only
+ */
+ public static final String BLASTULA_POOL_SECONDARY_SOCKET_NAME = "blastula_pool_secondary";
+
+ /**
+ * @hide for internal use only
+ */
private static final String LOG_TAG = "ZygoteProcess";
/**
* The name of the socket used to communicate with the primary zygote.
*/
- private final LocalSocketAddress mSocket;
+ private final LocalSocketAddress mZygoteSocketAddress;
/**
* The name of the secondary (alternate ABI) zygote socket.
*/
- private final LocalSocketAddress mSecondarySocket;
+ private final LocalSocketAddress mZygoteSecondarySocketAddress;
+ /**
+ * The name of the socket used to communicate with the primary blastula pool.
+ */
+ private final LocalSocketAddress mBlastulaPoolSocketAddress;
- public ZygoteProcess(String primarySocket, String secondarySocket) {
- this(new LocalSocketAddress(primarySocket, LocalSocketAddress.Namespace.RESERVED),
- new LocalSocketAddress(secondarySocket, LocalSocketAddress.Namespace.RESERVED));
+ /**
+ * The name of the socket used to communicate with the secondary (alternate ABI) blastula pool.
+ */
+ private final LocalSocketAddress mBlastulaPoolSecondarySocketAddress;
+
+ public ZygoteProcess() {
+ mZygoteSocketAddress =
+ new LocalSocketAddress(ZYGOTE_SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED);
+ mZygoteSecondarySocketAddress =
+ new LocalSocketAddress(ZYGOTE_SECONDARY_SOCKET_NAME,
+ LocalSocketAddress.Namespace.RESERVED);
+
+ mBlastulaPoolSocketAddress =
+ new LocalSocketAddress(BLASTULA_POOL_SOCKET_NAME,
+ LocalSocketAddress.Namespace.RESERVED);
+ mBlastulaPoolSecondarySocketAddress =
+ new LocalSocketAddress(BLASTULA_POOL_SECONDARY_SOCKET_NAME,
+ LocalSocketAddress.Namespace.RESERVED);
}
- public ZygoteProcess(LocalSocketAddress primarySocket, LocalSocketAddress secondarySocket) {
- mSocket = primarySocket;
- mSecondarySocket = secondarySocket;
+ public ZygoteProcess(LocalSocketAddress primarySocketAddress,
+ LocalSocketAddress secondarySocketAddress) {
+ mZygoteSocketAddress = primarySocketAddress;
+ mZygoteSecondarySocketAddress = secondarySocketAddress;
+
+ mBlastulaPoolSocketAddress = null;
+ mBlastulaPoolSecondarySocketAddress = null;
}
public LocalSocketAddress getPrimarySocketAddress() {
- return mSocket;
+ return mZygoteSocketAddress;
}
/**
* State for communicating with the zygote process.
*/
public static class ZygoteState {
- final LocalSocket socket;
- final DataInputStream inputStream;
- final BufferedWriter writer;
- final List<String> abiList;
+ final LocalSocketAddress mZygoteSocketAddress;
+ final LocalSocketAddress mBlastulaSocketAddress;
- boolean mClosed;
+ private final LocalSocket mZygoteSessionSocket;
- private ZygoteState(LocalSocket socket, DataInputStream inputStream,
- BufferedWriter writer, List<String> abiList) {
- this.socket = socket;
- this.inputStream = inputStream;
- this.writer = writer;
- this.abiList = abiList;
+ final DataInputStream mZygoteInputStream;
+ final BufferedWriter mZygoteOutputWriter;
+
+ private final List<String> mABIList;
+
+ private boolean mClosed;
+
+ private ZygoteState(LocalSocketAddress zygoteSocketAddress,
+ LocalSocketAddress blastulaSocketAddress,
+ LocalSocket zygoteSessionSocket,
+ DataInputStream zygoteInputStream,
+ BufferedWriter zygoteOutputWriter,
+ List<String> abiList) {
+ this.mZygoteSocketAddress = zygoteSocketAddress;
+ this.mBlastulaSocketAddress = blastulaSocketAddress;
+ this.mZygoteSessionSocket = zygoteSessionSocket;
+ this.mZygoteInputStream = zygoteInputStream;
+ this.mZygoteOutputWriter = zygoteOutputWriter;
+ this.mABIList = abiList;
}
- public static ZygoteState connect(LocalSocketAddress address) throws IOException {
+ /**
+ * Create a new ZygoteState object by connecting to the given Zygote socket and saving the
+ * given blastula socket address.
+ *
+ * @param zygoteSocketAddress Zygote socket to connect to
+ * @param blastulaSocketAddress Blastula socket address to save for later
+ * @return A new ZygoteState object containing a session socket for the given Zygote socket
+ * address
+ * @throws IOException
+ */
+ public static ZygoteState connect(LocalSocketAddress zygoteSocketAddress,
+ LocalSocketAddress blastulaSocketAddress)
+ throws IOException {
+
DataInputStream zygoteInputStream = null;
- BufferedWriter zygoteWriter = null;
- final LocalSocket zygoteSocket = new LocalSocket();
+ BufferedWriter zygoteOutputWriter = null;
+ final LocalSocket zygoteSessionSocket = new LocalSocket();
try {
- zygoteSocket.connect(address);
-
- zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
-
- zygoteWriter = new BufferedWriter(new OutputStreamWriter(
- zygoteSocket.getOutputStream()), 256);
+ zygoteSessionSocket.connect(zygoteSocketAddress);
+ zygoteInputStream = new DataInputStream(zygoteSessionSocket.getInputStream());
+ zygoteOutputWriter =
+ new BufferedWriter(
+ new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
+ Zygote.SOCKET_BUFFER_SIZE);
} catch (IOException ex) {
try {
- zygoteSocket.close();
- } catch (IOException ignore) {
- }
+ zygoteSessionSocket.close();
+ } catch (IOException ignore) { }
throw ex;
}
- String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
- Log.i("Zygote", "Process: zygote socket " + address.getNamespace() + "/"
- + address.getName() + " opened, supported ABIS: " + abiListString);
+ return new ZygoteState(zygoteSocketAddress, blastulaSocketAddress,
+ zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter,
+ getAbiList(zygoteOutputWriter, zygoteInputStream));
+ }
- return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
- Arrays.asList(abiListString.split(",")));
+ LocalSocket getBlastulaSessionSocket() throws IOException {
+ final LocalSocket blastulaSessionSocket = new LocalSocket();
+ blastulaSessionSocket.connect(this.mBlastulaSocketAddress);
+
+ return blastulaSessionSocket;
}
boolean matches(String abi) {
- return abiList.contains(abi);
+ return mABIList.contains(abi);
}
public void close() {
try {
- socket.close();
+ mZygoteSessionSocket.close();
} catch (IOException ex) {
Log.e(LOG_TAG,"I/O exception on routine close", ex);
}
@@ -237,12 +311,13 @@
@Nullable String packageName,
@Nullable String[] packagesForUid,
@Nullable String[] visibleVols,
+ boolean useBlastulaPool,
@Nullable String[] zygoteArgs) {
try {
return startViaZygote(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
- abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */,
- packageName, packagesForUid, visibleVols, zygoteArgs);
+ abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/false,
+ packageName, packagesForUid, visibleVols, useBlastulaPool, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
@@ -260,7 +335,7 @@
* @throws ZygoteStartFailedEx if the query failed.
*/
@GuardedBy("mLock")
- private static String getAbiList(BufferedWriter writer, DataInputStream inputStream)
+ private static List<String> getAbiList(BufferedWriter writer, DataInputStream inputStream)
throws IOException {
// Each query starts with the argument count (1 in this case)
writer.write("1");
@@ -276,7 +351,9 @@
byte[] bytes = new byte[numBytes];
inputStream.readFully(bytes);
- return new String(bytes, StandardCharsets.US_ASCII);
+ String rawList = new String(bytes, StandardCharsets.US_ASCII);
+
+ return Arrays.asList(rawList.split(","));
}
/**
@@ -288,59 +365,128 @@
*/
@GuardedBy("mLock")
private static Process.ProcessStartResult zygoteSendArgsAndGetResult(
- ZygoteState zygoteState, ArrayList<String> args)
+ ZygoteState zygoteState, boolean useBlastulaPool, ArrayList<String> args)
throws ZygoteStartFailedEx {
- try {
- // Throw early if any of the arguments are malformed. This means we can
- // avoid writing a partial response to the zygote.
- int sz = args.size();
- for (int i = 0; i < sz; i++) {
- if (args.get(i).indexOf('\n') >= 0) {
- throw new ZygoteStartFailedEx("embedded newlines not allowed");
+ // Throw early if any of the arguments are malformed. This means we can
+ // avoid writing a partial response to the zygote.
+ for (String arg : args) {
+ if (arg.indexOf('\n') >= 0) {
+ throw new ZygoteStartFailedEx("embedded newlines not allowed");
+ }
+ }
+
+ /**
+ * See com.android.internal.os.SystemZygoteInit.readArgumentList()
+ * Presently the wire format to the zygote process is:
+ * a) a count of arguments (argc, in essence)
+ * b) a number of newline-separated argument strings equal to count
+ *
+ * After the zygote process reads these it will write the pid of
+ * the child or -1 on failure, followed by boolean to
+ * indicate whether a wrapper process was used.
+ */
+ String msgStr = Integer.toString(args.size()) + "\n"
+ + String.join("\n", args) + "\n";
+
+ // Should there be a timeout on this?
+ Process.ProcessStartResult result = new Process.ProcessStartResult();
+
+ // TODO (chriswailes): Move branch body into separate function.
+ if (useBlastulaPool && Zygote.BLASTULA_POOL_ENABLED && isValidBlastulaCommand(args)) {
+ LocalSocket blastulaSessionSocket = null;
+
+ try {
+ blastulaSessionSocket = zygoteState.getBlastulaSessionSocket();
+
+ final BufferedWriter blastulaWriter =
+ new BufferedWriter(
+ new OutputStreamWriter(blastulaSessionSocket.getOutputStream()),
+ Zygote.SOCKET_BUFFER_SIZE);
+ final DataInputStream blastulaReader =
+ new DataInputStream(blastulaSessionSocket.getInputStream());
+
+ blastulaWriter.write(msgStr);
+ blastulaWriter.flush();
+
+ result.pid = blastulaReader.readInt();
+ // Blastulas can't be used to spawn processes that need wrappers.
+ result.usingWrapper = false;
+
+ if (result.pid < 0) {
+ throw new ZygoteStartFailedEx("Blastula specialization failed");
+ }
+
+ return result;
+ } catch (IOException ex) {
+ // If there was an IOException using the blastula pool we will log the error and
+ // attempt to start the process through the Zygote.
+ Log.e(LOG_TAG, "IO Exception while communicating with blastula pool - "
+ + ex.toString());
+ } finally {
+ try {
+ blastulaSessionSocket.close();
+ } catch (IOException ex) {
+ Log.e(LOG_TAG, "Failed to close blastula session socket: " + ex.getMessage());
}
}
+ }
- /**
- * See com.android.internal.os.SystemZygoteInit.readArgumentList()
- * Presently the wire format to the zygote process is:
- * a) a count of arguments (argc, in essence)
- * b) a number of newline-separated argument strings equal to count
- *
- * After the zygote process reads these it will write the pid of
- * the child or -1 on failure, followed by boolean to
- * indicate whether a wrapper process was used.
- */
- final BufferedWriter writer = zygoteState.writer;
- final DataInputStream inputStream = zygoteState.inputStream;
+ try {
+ final BufferedWriter zygoteWriter = zygoteState.mZygoteOutputWriter;
+ final DataInputStream zygoteInputStream = zygoteState.mZygoteInputStream;
- writer.write(Integer.toString(args.size()));
- writer.newLine();
-
- for (int i = 0; i < sz; i++) {
- String arg = args.get(i);
- writer.write(arg);
- writer.newLine();
- }
-
- writer.flush();
-
- // Should there be a timeout on this?
- Process.ProcessStartResult result = new Process.ProcessStartResult();
+ zygoteWriter.write(msgStr);
+ zygoteWriter.flush();
// Always read the entire result from the input stream to avoid leaving
// bytes in the stream for future process starts to accidentally stumble
// upon.
- result.pid = inputStream.readInt();
- result.usingWrapper = inputStream.readBoolean();
-
- if (result.pid < 0) {
- throw new ZygoteStartFailedEx("fork() failed");
- }
- return result;
+ result.pid = zygoteInputStream.readInt();
+ result.usingWrapper = zygoteInputStream.readBoolean();
} catch (IOException ex) {
zygoteState.close();
+ Log.e(LOG_TAG, "IO Exception while communicating with Zygote - "
+ + ex.toString());
throw new ZygoteStartFailedEx(ex);
}
+
+ if (result.pid < 0) {
+ throw new ZygoteStartFailedEx("fork() failed");
+ }
+
+ return result;
+ }
+
+ /**
+ * Flags that may not be passed to a blastula.
+ */
+ private static final String[] INVALID_BLASTULA_FLAGS = {
+ "--query-abi-list",
+ "--get-pid",
+ "--preload-default",
+ "--preload-package",
+ "--preload-app",
+ "--start-child-zygote",
+ "--set-api-blacklist-exemptions",
+ "--hidden-api-log-sampling-rate",
+ "--invoke-with"
+ };
+
+ /**
+ * Tests a command list to see if it is valid to send to a blastula.
+ * @param args Zygote/Blastula command arguments
+ * @return True if the command can be passed to a blastula; false otherwise
+ */
+ private static boolean isValidBlastulaCommand(ArrayList<String> args) {
+ for (String flag : args) {
+ for (String badFlag : INVALID_BLASTULA_FLAGS) {
+ if (flag.startsWith(badFlag)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
}
/**
@@ -382,6 +528,7 @@
@Nullable String packageName,
@Nullable String[] packagesForUid,
@Nullable String[] visibleVols,
+ boolean useBlastulaPool,
@Nullable String[] extraArgs)
throws ZygoteStartFailedEx {
ArrayList<String> argsForZygote = new ArrayList<String>();
@@ -488,7 +635,9 @@
}
synchronized(mLock) {
- return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
+ return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
+ useBlastulaPool,
+ argsForZygote);
}
}
@@ -528,18 +677,18 @@
ZygoteState state = openZygoteSocketIfNeeded(abi);
// Each query starts with the argument count (1 in this case)
- state.writer.write("1");
+ state.mZygoteOutputWriter.write("1");
// ... followed by a new-line.
- state.writer.newLine();
+ state.mZygoteOutputWriter.newLine();
// ... followed by our only argument.
- state.writer.write("--get-pid");
- state.writer.newLine();
- state.writer.flush();
+ state.mZygoteOutputWriter.write("--get-pid");
+ state.mZygoteOutputWriter.newLine();
+ state.mZygoteOutputWriter.flush();
// The response is a length prefixed stream of ASCII bytes.
- int numBytes = state.inputStream.readInt();
+ int numBytes = state.mZygoteInputStream.readInt();
byte[] bytes = new byte[numBytes];
- state.inputStream.readFully(bytes);
+ state.mZygoteInputStream.readFully(bytes);
return Integer.parseInt(new String(bytes, StandardCharsets.US_ASCII));
}
@@ -593,16 +742,16 @@
return true;
}
try {
- state.writer.write(Integer.toString(mApiBlacklistExemptions.size() + 1));
- state.writer.newLine();
- state.writer.write("--set-api-blacklist-exemptions");
- state.writer.newLine();
+ state.mZygoteOutputWriter.write(Integer.toString(mApiBlacklistExemptions.size() + 1));
+ state.mZygoteOutputWriter.newLine();
+ state.mZygoteOutputWriter.write("--set-api-blacklist-exemptions");
+ state.mZygoteOutputWriter.newLine();
for (int i = 0; i < mApiBlacklistExemptions.size(); ++i) {
- state.writer.write(mApiBlacklistExemptions.get(i));
- state.writer.newLine();
+ state.mZygoteOutputWriter.write(mApiBlacklistExemptions.get(i));
+ state.mZygoteOutputWriter.newLine();
}
- state.writer.flush();
- int status = state.inputStream.readInt();
+ state.mZygoteOutputWriter.flush();
+ int status = state.mZygoteInputStream.readInt();
if (status != 0) {
Slog.e(LOG_TAG, "Failed to set API blacklist exemptions; status " + status);
}
@@ -622,13 +771,13 @@
return;
}
try {
- state.writer.write(Integer.toString(1));
- state.writer.newLine();
- state.writer.write("--hidden-api-log-sampling-rate="
+ state.mZygoteOutputWriter.write(Integer.toString(1));
+ state.mZygoteOutputWriter.newLine();
+ state.mZygoteOutputWriter.write("--hidden-api-log-sampling-rate="
+ Integer.toString(mHiddenApiAccessLogSampleRate));
- state.writer.newLine();
- state.writer.flush();
- int status = state.inputStream.readInt();
+ state.mZygoteOutputWriter.newLine();
+ state.mZygoteOutputWriter.flush();
+ int status = state.mZygoteInputStream.readInt();
if (status != 0) {
Slog.e(LOG_TAG, "Failed to set hidden API log sampling rate; status " + status);
}
@@ -638,22 +787,29 @@
}
/**
- * Tries to open socket to Zygote process if not already open. If
- * already open, does nothing. May block and retry. Requires that mLock be held.
+ * Tries to open a session socket to a Zygote process with a compatible ABI if one is not
+ * already open. If a compatible session socket is already open that session socket is returned.
+ * This function may block and may have to try connecting to multiple Zygotes to find the
+ * appropriate one. Requires that mLock be held.
*/
@GuardedBy("mLock")
- private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
+ private ZygoteState openZygoteSocketIfNeeded(String abi)
+ throws ZygoteStartFailedEx {
+
Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held");
if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
try {
- primaryZygoteState = ZygoteState.connect(mSocket);
+ primaryZygoteState =
+ ZygoteState.connect(mZygoteSocketAddress, mBlastulaPoolSocketAddress);
} catch (IOException ioe) {
throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
}
+
maybeSetApiBlacklistExemptions(primaryZygoteState, false);
maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
}
+
if (primaryZygoteState.matches(abi)) {
return primaryZygoteState;
}
@@ -661,10 +817,13 @@
// The primary zygote didn't match. Try the secondary.
if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
try {
- secondaryZygoteState = ZygoteState.connect(mSecondarySocket);
+ secondaryZygoteState =
+ ZygoteState.connect(mZygoteSecondarySocketAddress,
+ mBlastulaPoolSecondarySocketAddress);
} catch (IOException ioe) {
throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
}
+
maybeSetApiBlacklistExemptions(secondaryZygoteState, false);
maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState);
}
@@ -685,11 +844,11 @@
IOException {
synchronized (mLock) {
ZygoteState state = openZygoteSocketIfNeeded(abi);
- state.writer.write("2");
- state.writer.newLine();
+ state.mZygoteOutputWriter.write("2");
+ state.mZygoteOutputWriter.newLine();
- state.writer.write("--preload-app");
- state.writer.newLine();
+ state.mZygoteOutputWriter.write("--preload-app");
+ state.mZygoteOutputWriter.newLine();
// Zygote args needs to be strings, so in order to pass ApplicationInfo,
// write it to a Parcel, and base64 the raw Parcel bytes to the other side.
@@ -697,12 +856,12 @@
appInfo.writeToParcel(parcel, 0 /* flags */);
String encodedParcelData = Base64.getEncoder().encodeToString(parcel.marshall());
parcel.recycle();
- state.writer.write(encodedParcelData);
- state.writer.newLine();
+ state.mZygoteOutputWriter.write(encodedParcelData);
+ state.mZygoteOutputWriter.newLine();
- state.writer.flush();
+ state.mZygoteOutputWriter.flush();
- return (state.inputStream.readInt() == 0);
+ return (state.mZygoteInputStream.readInt() == 0);
}
}
@@ -715,27 +874,27 @@
IOException {
synchronized(mLock) {
ZygoteState state = openZygoteSocketIfNeeded(abi);
- state.writer.write("5");
- state.writer.newLine();
+ state.mZygoteOutputWriter.write("5");
+ state.mZygoteOutputWriter.newLine();
- state.writer.write("--preload-package");
- state.writer.newLine();
+ state.mZygoteOutputWriter.write("--preload-package");
+ state.mZygoteOutputWriter.newLine();
- state.writer.write(packagePath);
- state.writer.newLine();
+ state.mZygoteOutputWriter.write(packagePath);
+ state.mZygoteOutputWriter.newLine();
- state.writer.write(libsPath);
- state.writer.newLine();
+ state.mZygoteOutputWriter.write(libsPath);
+ state.mZygoteOutputWriter.newLine();
- state.writer.write(libFileName);
- state.writer.newLine();
+ state.mZygoteOutputWriter.write(libFileName);
+ state.mZygoteOutputWriter.newLine();
- state.writer.write(cacheKey);
- state.writer.newLine();
+ state.mZygoteOutputWriter.write(cacheKey);
+ state.mZygoteOutputWriter.newLine();
- state.writer.flush();
+ state.mZygoteOutputWriter.flush();
- return (state.inputStream.readInt() == 0);
+ return (state.mZygoteInputStream.readInt() == 0);
}
}
@@ -749,13 +908,13 @@
synchronized (mLock) {
ZygoteState state = openZygoteSocketIfNeeded(abi);
// Each query starts with the argument count (1 in this case)
- state.writer.write("1");
- state.writer.newLine();
- state.writer.write("--preload-default");
- state.writer.newLine();
- state.writer.flush();
+ state.mZygoteOutputWriter.write("1");
+ state.mZygoteOutputWriter.newLine();
+ state.mZygoteOutputWriter.write("--preload-default");
+ state.mZygoteOutputWriter.newLine();
+ state.mZygoteOutputWriter.flush();
- return (state.inputStream.readInt() == 0);
+ return (state.mZygoteInputStream.readInt() == 0);
}
}
@@ -763,20 +922,21 @@
* Try connecting to the Zygote over and over again until we hit a time-out.
* @param socketName The name of the socket to connect to.
*/
- public static void waitForConnectionToZygote(String socketName) {
- final LocalSocketAddress address =
- new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.RESERVED);
- waitForConnectionToZygote(address);
+ public static void waitForConnectionToZygote(String zygoteSocketName) {
+ final LocalSocketAddress zygoteSocketAddress =
+ new LocalSocketAddress(zygoteSocketName, LocalSocketAddress.Namespace.RESERVED);
+ waitForConnectionToZygote(zygoteSocketAddress);
}
/**
* Try connecting to the Zygote over and over again until we hit a time-out.
* @param address The name of the socket to connect to.
*/
- public static void waitForConnectionToZygote(LocalSocketAddress address) {
+ public static void waitForConnectionToZygote(LocalSocketAddress zygoteSocketAddress) {
for (int n = 20; n >= 0; n--) {
try {
- final ZygoteState zs = ZygoteState.connect(address);
+ final ZygoteState zs =
+ ZygoteState.connect(zygoteSocketAddress, null);
zs.close();
return;
} catch (IOException ioe) {
@@ -789,7 +949,8 @@
} catch (InterruptedException ie) {
}
}
- Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + address.getName());
+ Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket "
+ + zygoteSocketAddress.getName());
}
/**
@@ -839,7 +1000,8 @@
gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo,
abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
true /* startChildZygote */, null /* packageName */,
- null /* packagesForUid */, null /* visibleVolumes */, extraArgs);
+ null /* packagesForUid */, null /* visibleVolumes */,
+ false /* useBlastulaPool */, extraArgs);
} catch (ZygoteStartFailedEx ex) {
throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
}
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index df1a713..714a061 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -35,6 +35,7 @@
import java.io.CharArrayWriter;
import java.io.File;
+import java.util.Locale;
/**
* Information about a shared/external storage volume for a specific user.
@@ -263,6 +264,11 @@
return mFsUuid;
}
+ /** {@hide} */
+ public @Nullable String getNormalizedUuid() {
+ return mFsUuid != null ? mFsUuid.toLowerCase(Locale.US) : null;
+ }
+
/**
* Parse and return volume UUID as FAT volume ID, or return -1 if unable to
* parse or UUID is unknown.
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 8c3aa17..5d310e1 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -42,6 +42,7 @@
import java.io.CharArrayWriter;
import java.io.File;
import java.util.Comparator;
+import java.util.Locale;
import java.util.Objects;
/**
@@ -254,6 +255,10 @@
return fsUuid;
}
+ public @Nullable String getNormalizedFsUuid() {
+ return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
+ }
+
@UnsupportedAppUsage
public int getMountUserId() {
return mountUserId;
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index c167ea1..8bd75d7 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -44,6 +44,8 @@
import com.android.internal.util.Preconditions;
+import java.util.Set;
+
/**
* <p>
* The contract between the calendar provider and applications. Contains
@@ -217,7 +219,7 @@
* The intent will have its action set to
* {@link CalendarContract#ACTION_VIEW_WORK_CALENDAR_EVENT} and contain extras
* corresponding to the API's arguments. A calendar app intending to support
- * cross profile events viewing should handle this intent, parse the arguments
+ * cross-profile events viewing should handle this intent, parse the arguments
* and show the appropriate UI.
*
* @param context the context.
@@ -767,9 +769,10 @@
* projection of the query to this uri that are not contained in the above list.
*
* <p>This uri will return an empty cursor if the calling user is not a parent profile
- * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is
+ * of a managed profile, or cross-profile calendar is disabled in Settings, or this uri is
* queried from a package that is not whitelisted by profile owner of the managed profile
- * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+ * via
+ * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}.
*
* @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
* @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
@@ -1758,9 +1761,10 @@
* projection of the query to this uri that are not contained in the above list.
*
* <p>This uri will return an empty cursor if the calling user is not a parent profile
- * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is
+ * of a managed profile, or cross-profile calendar is disabled in Settings, or this uri is
* queried from a package that is not whitelisted by profile owner of the managed profile
- * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+ * via
+ * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}.
*
* @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
* @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
@@ -1968,10 +1972,10 @@
* projection of the query to this uri that are not contained in the above list.
*
* <p>This uri will return an empty cursor if the calling user is not a parent profile
- * of a managed profile, or cross profile calendar for the managed profile is disabled in
+ * of a managed profile, or cross-profile calendar for the managed profile is disabled in
* Settings, or this uri is queried from a package that is not whitelisted by
* profile owner of the managed profile via
- * {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+ * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}.
*
* @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
* @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index a7e6601..6ccd296 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -54,6 +54,7 @@
/**
* Namespace for all Game Driver features.
+ *
* @hide
*/
@SystemApi
@@ -94,6 +95,64 @@
@SystemApi
public static final String NAMESPACE_NETD_NATIVE = "netd_native";
+ /**
+ * Namespace for features related to the ExtServices Notification Assistant.
+ * These features are applied immediately.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant";
+
+ /**
+ * Namespace for attention-based features provided by on-device machine intelligence.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface IntelligenceAttention {
+ String NAMESPACE = "intelligence_attention";
+ /**
+ * If {@code true}, enables the attention check.
+ */
+ String PROPERTY_ATTENTION_CHECK_ENABLED = "attention_check_enabled";
+ /**
+ * Settings for performing the attention check.
+ */
+ String PROPERTY_ATTENTION_CHECK_SETTINGS = "attention_check_settings";
+ }
+
+ /**
+ * Telephony related properties definitions.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface Telephony {
+ String NAMESPACE = "telephony";
+ /**
+ * Whether to apply ramping ringer on incoming phone calls.
+ */
+ String PROPERTY_ENABLE_RAMPING_RINGER = "enable_ramping_ringer";
+ /**
+ * Ringer ramping time in milliseconds.
+ */
+ String PROPERTY_RAMPING_RINGER_DURATION = "ramping_duration";
+ }
+
+ /**
+ * Namespace for Full Stack Integrity to run privileged apps only in JIT mode. The flag applies
+ * at process start, so reboot is a way to bring the device to a clean state.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface FsiBoot {
+ String NAMESPACE = "fsi_boot";
+ String OOB_ENABLED = "oob_enabled";
+ String OOB_WHITELIST = "oob_whitelist";
+ }
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
@@ -109,9 +168,8 @@
* Look up the value of a property for a particular namespace.
*
* @param namespace The namespace containing the property to look up.
- * @param name The name of the property to look up.
+ * @param name The name of the property to look up.
* @return the corresponding value, or null if not present.
- *
* @hide
*/
@SystemApi
@@ -133,14 +191,13 @@
* All properties stored for a particular scope can be reverted to their default values
* by passing the namespace to {@link #resetToDefaults(int, String)}.
*
- * @param namespace The namespace containing the property to create or update.
- * @param name The name of the property to create or update.
- * @param value The value to store for the property.
+ * @param namespace The namespace containing the property to create or update.
+ * @param name The name of the property to create or update.
+ * @param value The value to store for the property.
* @param makeDefault Whether to make the new value the default one.
* @return True if the value was set, false if the storage implementation throws errors.
- * @see #resetToDefaults(int, String).
- *
* @hide
+ * @see #resetToDefaults(int, String).
*/
@SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
@@ -159,9 +216,8 @@
*
* @param resetMode The reset mode to use.
* @param namespace Optionally, the specific namespace which resets will be limited to.
- * @see #setProperty(String, String, String, boolean)
- *
* @hide
+ * @see #setProperty(String, String, String, boolean)
*/
@SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
@@ -178,12 +234,11 @@
* will replace the old namespace and executor. Remove the listener entirely by calling
* {@link #removeOnPropertyChangedListener(OnPropertyChangedListener)}.
*
- * @param namespace The namespace containing properties to monitor.
- * @param executor The executor which will be used to run callbacks.
+ * @param namespace The namespace containing properties to monitor.
+ * @param executor The executor which will be used to run callbacks.
* @param onPropertyChangedListener The listener to add.
- * @see #removeOnPropertyChangedListener(OnPropertyChangedListener)
- *
* @hide
+ * @see #removeOnPropertyChangedListener(OnPropertyChangedListener)
*/
@SystemApi
@RequiresPermission(READ_DEVICE_CONFIG)
@@ -215,9 +270,8 @@
* property changes.
*
* @param onPropertyChangedListener The listener to remove.
- * @see #addOnPropertyChangedListener(String, Executor, OnPropertyChangedListener)
- *
* @hide
+ * @see #addOnPropertyChangedListener(String, Executor, OnPropertyChangedListener)
*/
@SystemApi
public static void removeOnPropertyChangedListener(
@@ -318,8 +372,8 @@
* Called when a property has changed.
*
* @param namespace The namespace containing the property which has changed.
- * @param name The name of the property which has changed.
- * @param value The new value of the property which has changed.
+ * @param name The name of the property which has changed.
+ * @param value The new value of the property which has changed.
*/
void onPropertyChanged(String namespace, String name, String value);
}
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index a323ed1..5f1c560 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -16,7 +16,6 @@
package android.provider;
-import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
import static com.android.internal.util.Preconditions.checkCollectionNotEmpty;
@@ -50,6 +49,8 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import dalvik.system.VMRuntime;
import java.io.File;
@@ -640,6 +641,28 @@
public static final String COLUMN_MIME_TYPES = "mime_types";
/**
+ * Query arguments supported by this root. This column is optional
+ * and related to {@link #COLUMN_FLAGS} and {@link #FLAG_SUPPORTS_SEARCH}.
+ * If the flags include {@link #FLAG_SUPPORTS_SEARCH}, and the column is
+ * {@code null}, the root is assumed to support {@link #QUERY_ARG_DISPLAY_NAME}
+ * search of {@link Document#COLUMN_DISPLAY_NAME}. Multiple query arguments
+ * can be separated by a newline. For example, a root supporting
+ * {@link #QUERY_ARG_MIME_TYPES} and {@link #QUERY_ARG_DISPLAY_NAME} might
+ * return "android:query-arg-mime-types\nandroid:query-arg-display-name".
+ * <p>
+ * Type: STRING
+ * @see #COLUMN_FLAGS
+ * @see #FLAG_SUPPORTS_SEARCH
+ * @see #QUERY_ARG_DISPLAY_NAME
+ * @see #QUERY_ARG_FILE_SIZE_OVER
+ * @see #QUERY_ARG_LAST_MODIFIED_AFTER
+ * @see #QUERY_ARG_MIME_TYPES
+ * @see DocumentsProvider#querySearchDocuments(String, String[],
+ * Bundle)
+ */
+ public static final String COLUMN_QUERY_ARGS = "query_args";
+
+ /**
* MIME type for a root.
*/
public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
@@ -680,6 +703,8 @@
* String)
* @see DocumentsProvider#querySearchDocuments(String, String,
* String[])
+ * @see DocumentsProvider#querySearchDocuments(String, String[],
+ * Bundle)
*/
public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
@@ -1109,11 +1134,13 @@
}
/**
- * Test if the given URI represents roots backed by {@link DocumentsProvider}.
+ * Test if the given URI represents all roots of the authority
+ * backed by {@link DocumentsProvider}.
*
* @see #buildRootsUri(String)
*/
- public static boolean isRootsUri(Context context, @Nullable Uri uri) {
+ public static boolean isRootsUri(@NonNull Context context, @Nullable Uri uri) {
+ Preconditions.checkNotNull(context, "context can not be null");
return isRootUri(context, uri, 1 /* pathSize */);
}
@@ -1122,7 +1149,8 @@
*
* @see #buildRootUri(String, String)
*/
- public static boolean isRootUri(Context context, @Nullable Uri uri) {
+ public static boolean isRootUri(@NonNull Context context, @Nullable Uri uri) {
+ Preconditions.checkNotNull(context, "context can not be null");
return isRootUri(context, uri, 2 /* pathSize */);
}
@@ -1215,6 +1243,7 @@
* {@hide}
*/
public static String getSearchDocumentsQuery(@NonNull Bundle bundle) {
+ Preconditions.checkNotNull(bundle, "bundle can not be null");
return bundle.getString(QUERY_ARG_DISPLAY_NAME, "" /* defaultValue */);
}
@@ -1315,8 +1344,12 @@
* @return if given document is a descendant of the given parent.
* @see Root#FLAG_SUPPORTS_IS_CHILD
*/
- public static boolean isChildDocument(ContentInterface content, Uri parentDocumentUri,
- Uri childDocumentUri) throws FileNotFoundException {
+ public static boolean isChildDocument(@NonNull ContentInterface content,
+ @NonNull Uri parentDocumentUri, @NonNull Uri childDocumentUri)
+ throws FileNotFoundException {
+ Preconditions.checkNotNull(content, "content can not be null");
+ Preconditions.checkNotNull(parentDocumentUri, "parentDocumentUri can not be null");
+ Preconditions.checkNotNull(childDocumentUri, "childDocumentUri can not be null");
try {
final Bundle in = new Bundle();
in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
@@ -1325,7 +1358,7 @@
final Bundle out = content.call(parentDocumentUri.getAuthority(),
METHOD_IS_CHILD_DOCUMENT, null, in);
if (out == null) {
- throw new RemoteException("Failed to get a reponse from isChildDocument query.");
+ throw new RemoteException("Failed to get a response from isChildDocument query.");
}
if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
throw new RemoteException("Response did not include result field..");
@@ -1559,8 +1592,10 @@
* @param documentUri a Document URI
* @return a Bundle of Bundles.
*/
- public static Bundle getDocumentMetadata(ContentInterface content, Uri documentUri)
- throws FileNotFoundException {
+ public static @Nullable Bundle getDocumentMetadata(@NonNull ContentInterface content,
+ @NonNull Uri documentUri) throws FileNotFoundException {
+ Preconditions.checkNotNull(content, "content can not be null");
+ Preconditions.checkNotNull(documentUri, "documentUri can not be null");
try {
final Bundle in = new Bundle();
in.putParcelable(EXTRA_URI, documentUri);
@@ -1595,8 +1630,6 @@
*/
public static Path findDocumentPath(ContentInterface content, Uri treeUri)
throws FileNotFoundException {
- checkArgument(isTreeUri(treeUri), treeUri + " is not a tree uri.");
-
try {
final Bundle in = new Bundle();
in.putParcelable(DocumentsContract.EXTRA_URI, treeUri);
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 70c84f8..9b9e2de 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -37,6 +37,7 @@
import android.Manifest;
import android.annotation.CallSuper;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AuthenticationRequiredException;
import android.content.ClipDescription;
@@ -63,6 +64,8 @@
import android.provider.DocumentsContract.Root;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import libcore.io.IoUtils;
import java.io.FileNotFoundException;
@@ -687,14 +690,17 @@
* extras {@link Bundle} when any QUERY_ARG_* value was honored
* during the preparation of the results.
*
+ * @see Root#COLUMN_QUERY_ARGS
* @see ContentResolver#EXTRA_HONORED_ARGS
* @see DocumentsContract#EXTRA_LOADING
* @see DocumentsContract#EXTRA_INFO
* @see DocumentsContract#EXTRA_ERROR
*/
@SuppressWarnings("unused")
- public Cursor querySearchDocuments(String rootId, String[] projection, Bundle queryArgs)
- throws FileNotFoundException {
+ public Cursor querySearchDocuments(@NonNull String rootId, @Nullable String[] projection,
+ @NonNull Bundle queryArgs) throws FileNotFoundException {
+ Preconditions.checkNotNull(rootId, "rootId can not be null");
+ Preconditions.checkNotNull(queryArgs, "queryArgs can not be null");
return querySearchDocuments(rootId, DocumentsContract.getSearchDocumentsQuery(queryArgs),
projection);
}
@@ -732,7 +738,7 @@
* @return a Bundle of Bundles.
* @see DocumentsContract#getDocumentMetadata(ContentResolver, Uri)
*/
- public @Nullable Bundle getDocumentMetadata(String documentId)
+ public @Nullable Bundle getDocumentMetadata(@NonNull String documentId)
throws FileNotFoundException {
throw new UnsupportedOperationException("Metadata not supported");
}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 3a49986..f5c442f 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -70,6 +70,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -87,9 +89,19 @@
/** A content:// style uri to the authority for the media provider */
public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
- /** {@hide} */
+ /**
+ * Volume name used for content on "internal" storage of device. This
+ * volume contains media distributed with the device, such as built-in
+ * ringtones and wallpapers.
+ */
public static final String VOLUME_INTERNAL = "internal";
- /** {@hide} */
+
+ /**
+ * Volume name used for content on "external" storage of device. This only
+ * includes media on the primary shared storage device; the contents of any
+ * secondary storage devices can be obtained using
+ * {@link #getAllVolumeNames(Context)}.
+ */
public static final String VOLUME_EXTERNAL = "external";
/**
@@ -1224,7 +1236,7 @@
if (sv.isPrimary()) {
return VOLUME_EXTERNAL;
} else {
- return checkArgumentVolumeName(sv.getUuid());
+ return checkArgumentVolumeName(sv.getNormalizedUuid());
}
}
throw new IllegalStateException("Unknown volume at " + path);
@@ -1564,7 +1576,13 @@
/**
* This class provides utility methods to obtain thumbnails for various
* {@link Images} items.
+ *
+ * @deprecated Callers should migrate to using
+ * {@link ContentResolver#loadThumbnail}, since it offers
+ * richer control over requested thumbnail sizes and
+ * cancellation behavior.
*/
+ @Deprecated
public static class Thumbnails implements BaseColumns {
public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
@@ -2741,7 +2759,13 @@
/**
* This class provides utility methods to obtain thumbnails for various
* {@link Video} items.
+ *
+ * @deprecated Callers should migrate to using
+ * {@link ContentResolver#loadThumbnail}, since it offers
+ * richer control over requested thumbnail sizes and
+ * cancellation behavior.
*/
+ @Deprecated
public static class Thumbnails implements BaseColumns {
/**
* Cancel any outstanding {@link #getThumbnail} requests, causing
@@ -2919,7 +2943,7 @@
if (vi.isPrimary()) {
volumeNames.add(VOLUME_EXTERNAL);
} else {
- volumeNames.add(vi.getFsUuid());
+ volumeNames.add(vi.getNormalizedFsUuid());
}
}
}
@@ -2953,8 +2977,7 @@
// When not one of the well-known values above, it must be a hex UUID
for (int i = 0; i < volumeName.length(); i++) {
final char c = volumeName.charAt(i);
- if (('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
- || ('0' <= c && c <= '9') || (c == '-')) {
+ if (('a' <= c && c <= 'f') || ('0' <= c && c <= '9') || (c == '-')) {
continue;
} else {
throw new IllegalArgumentException("Invalid volume name: " + volumeName);
@@ -2963,23 +2986,26 @@
return volumeName;
}
- /** {@hide} */
+ /**
+ * Return path where the given volume is mounted. Not valid for
+ * {@link #VOLUME_INTERNAL}.
+ *
+ * @hide
+ */
public static @NonNull File getVolumePath(@NonNull String volumeName)
throws FileNotFoundException {
if (TextUtils.isEmpty(volumeName)) {
throw new IllegalArgumentException();
}
- if (VOLUME_INTERNAL.equals(volumeName)) {
- return Environment.getDataDirectory();
- } else if (VOLUME_EXTERNAL.equals(volumeName)) {
+ if (VOLUME_EXTERNAL.equals(volumeName)) {
return Environment.getExternalStorageDirectory();
}
final StorageManager sm = AppGlobals.getInitialApplication()
.getSystemService(StorageManager.class);
for (VolumeInfo vi : sm.getVolumes()) {
- if (Objects.equals(vi.getFsUuid(), volumeName)) {
+ if (Objects.equals(vi.getNormalizedFsUuid(), volumeName)) {
final File path = vi.getPathForUser(UserHandle.myUserId());
if (path != null) {
return path;
@@ -2992,6 +3018,33 @@
}
/**
+ * Return paths that should be scanned for the given volume.
+ *
+ * @hide
+ */
+ public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
+ throws FileNotFoundException {
+ if (TextUtils.isEmpty(volumeName)) {
+ throw new IllegalArgumentException();
+ }
+
+ final ArrayList<File> res = new ArrayList<>();
+ if (VOLUME_INTERNAL.equals(volumeName)) {
+ res.add(new File(Environment.getRootDirectory(), "media"));
+ res.add(new File(Environment.getOemDirectory(), "media"));
+ res.add(new File(Environment.getProductDirectory(), "media"));
+ } else {
+ res.add(getVolumePath(volumeName));
+ final UserManager um = AppGlobals.getInitialApplication()
+ .getSystemService(UserManager.class);
+ if (VOLUME_EXTERNAL.equals(volumeName) && um.isDemoUser()) {
+ res.add(Environment.getDataPreloadsMediaDirectory());
+ }
+ }
+ return res;
+ }
+
+ /**
* Uri for querying the state of the media scanner.
*/
public static Uri getMediaScannerUri() {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 516f49c..bdeacdf 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3300,6 +3300,14 @@
public static final int SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1;
/**
+ * Control whether to enable adaptive sleep mode.
+ * @hide
+ */
+ public static final String ADAPTIVE_SLEEP = "adaptive_sleep";
+
+ private static final Validator ADAPTIVE_SLEEP_VALIDATOR = BOOLEAN_VALIDATOR;
+
+ /**
* Control whether the process CPU usage meter should be shown.
*
* @deprecated This functionality is no longer available as of
@@ -4232,6 +4240,7 @@
SCREEN_BRIGHTNESS_MODE,
SCREEN_AUTO_BRIGHTNESS_ADJ,
SCREEN_BRIGHTNESS_FOR_VR,
+ ADAPTIVE_SLEEP,
VIBRATE_INPUT_DEVICES,
MODE_RINGER_STREAMS_AFFECTED,
TEXT_AUTO_REPLACE,
@@ -4307,6 +4316,7 @@
PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS);
PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_FOR_VR);
PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_MODE);
+ PUBLIC_SETTINGS.add(ADAPTIVE_SLEEP);
PUBLIC_SETTINGS.add(MODE_RINGER_STREAMS_AFFECTED);
PUBLIC_SETTINGS.add(MUTE_STREAMS_AFFECTED);
PUBLIC_SETTINGS.add(VIBRATE_ON);
@@ -4411,6 +4421,7 @@
VALIDATORS.put(SCREEN_OFF_TIMEOUT, SCREEN_OFF_TIMEOUT_VALIDATOR);
VALIDATORS.put(SCREEN_BRIGHTNESS_FOR_VR, SCREEN_BRIGHTNESS_FOR_VR_VALIDATOR);
VALIDATORS.put(SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_VALIDATOR);
+ VALIDATORS.put(ADAPTIVE_SLEEP, ADAPTIVE_SLEEP_VALIDATOR);
VALIDATORS.put(MODE_RINGER_STREAMS_AFFECTED, MODE_RINGER_STREAMS_AFFECTED_VALIDATOR);
VALIDATORS.put(MUTE_STREAMS_AFFECTED, MUTE_STREAMS_AFFECTED_VALIDATOR);
VALIDATORS.put(VIBRATE_ON, VIBRATE_ON_VALIDATOR);
@@ -8410,6 +8421,27 @@
public static final String LOCATION_ACCESS_CHECK_DELAY_MILLIS =
"location_access_check_delay_millis";
+
+ /**
+ * Comma separated list of enabled overlay packages for all android.theme.customization.*
+ * categories. If there is no corresponding package included for a category, then all
+ * overlay packages in that category must be disabled.
+ * @hide
+ */
+ @SystemApi
+ public static final String THEME_CUSTOMIZATION_OVERLAY_PACKAGES =
+ "theme_customization_overlay_packages";
+
+ private static final Validator THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR =
+ new SettingsValidators.PackageNameListValidator(",");
+
+ /**
+ * Controls whether aware is enabled.
+ * @hide
+ */
+ public static final String AWARE_ENABLED = "aware_enabled";
+
+ private static final Validator AWARE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
/**
* This are the settings to be backed up.
*
@@ -8533,6 +8565,8 @@
LOCK_SCREEN_WHEN_TRUST_LOST,
SKIP_GESTURE,
SILENCE_GESTURE,
+ THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ AWARE_ENABLED,
};
/**
@@ -8703,6 +8737,9 @@
VALIDATORS.put(LOCK_SCREEN_WHEN_TRUST_LOST, LOCK_SCREEN_WHEN_TRUST_LOST_VALIDATOR);
VALIDATORS.put(SKIP_GESTURE, SKIP_GESTURE_VALIDATOR);
VALIDATORS.put(SILENCE_GESTURE, SILENCE_GESTURE_VALIDATOR);
+ VALIDATORS.put(THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR);
+ VALIDATORS.put(AWARE_ENABLED, AWARE_ENABLED_VALIDATOR);
}
/**
@@ -9441,23 +9478,6 @@
"hdmi_control_auto_device_off_enabled";
/**
- * If <b>true</b>, enables out-of-the-box execution for priv apps.
- * Default: false
- * Values: 0 = false, 1 = true
- *
- * @hide
- */
- public static final String PRIV_APP_OOB_ENABLED = "priv_app_oob_enabled";
-
- /**
- * Comma separated list of privileged package names, which will be running out-of-box APK.
- * Default: "ALL"
- *
- * @hide
- */
- public static final String PRIV_APP_OOB_LIST = "priv_app_oob_list";
-
- /**
* The interval in milliseconds at which location requests will be throttled when they are
* coming from the background.
*
@@ -9482,6 +9502,14 @@
"location_background_throttle_package_whitelist";
/**
+ * Packages that are whitelisted for ignoring location settings (may retrieve location even
+ * when user location settings are off), for emergency purposes.
+ * @hide
+ */
+ public static final String LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST =
+ "location_ignore_settings_package_whitelist";
+
+ /**
* Whether to disable location status callbacks in preparation for deprecation.
* @hide
*/
@@ -11495,6 +11523,20 @@
public static final String DISPLAY_PANEL_LPM = "display_panel_lpm";
/**
+ * App time limit usage source setting.
+ * This controls which app in a task will be considered the source of usage when
+ * calculating app usage time limits.
+ *
+ * 1 -> task root app
+ * 2 -> current app
+ * Any other value defaults to task root app.
+ *
+ * Need to reboot the device for this setting to take effect.
+ * @hide
+ */
+ public static final String APP_TIME_LIMIT_USAGE_SOURCE = "app_time_limit_usage_source";
+
+ /**
* App standby (app idle) specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
* <p>
@@ -11856,6 +11898,28 @@
public static final String KEEP_PROFILE_IN_BACKGROUND = "keep_profile_in_background";
/**
+ * The default time in ms within which a subsequent connection from an always allowed system
+ * is allowed to reconnect without user interaction.
+ *
+ * @hide
+ */
+ public static final long DEFAULT_ADB_ALLOWED_CONNECTION_TIME = 604800000;
+
+ /**
+ * When the user first connects their device to a system a prompt is displayed to allow
+ * the adb connection with an option to 'Always allow' connections from this system. If the
+ * user selects this always allow option then the connection time is stored for the system.
+ * This setting is the time in ms within which a subsequent connection from an always
+ * allowed system is allowed to reconnect without user interaction.
+ *
+ * Type: long
+ *
+ * @hide
+ */
+ public static final String ADB_ALLOWED_CONNECTION_TIME =
+ "adb_allowed_connection_time";
+
+ /**
* Get the key that retrieves a bluetooth headset's priority.
* @hide
*/
@@ -12103,6 +12167,14 @@
public static final String GUP_BLACKLIST = "gup_blacklist";
/**
+ * Apps on the whitelist that are allowed to use Game Driver.
+ * The string is a list of application package names, seperated by comma.
+ * i.e. <apk1>,<apk2>,...,<apkN>
+ * @hide
+ */
+ public static final String GAME_DRIVER_WHITELIST = "game_driver_whitelist";
+
+ /**
* Ordered GPU debug layer list for Vulkan
* i.e. <layer1>:<layer2>:...:<layerN>
* @hide
@@ -12147,6 +12219,31 @@
public static final String LOW_POWER_MODE_STICKY = "low_power_sticky";
/**
+ * When a device is unplugged from a changer (or is rebooted), do not re-activate battery
+ * saver even if {@link #LOW_POWER_MODE_STICKY} is 1, if the battery level is equal to or
+ * above this threshold.
+ *
+ * @hide
+ */
+ public static final String LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL =
+ "low_power_sticky_auto_disable_level";
+
+ private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL_VALIDATOR =
+ new SettingsValidators.InclusiveIntegerRangeValidator(0, 100);
+
+ /**
+ * Whether sticky battery saver should be deactivated once the battery level has reached the
+ * threshold specified by {@link #LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL}.
+ *
+ * @hide
+ */
+ public static final String LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED =
+ "low_power_sticky_auto_disable_enabled";
+
+ private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED_VALIDATOR =
+ new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1"});
+
+ /**
* Battery level [1-100] at which low power mode automatically turns on.
* Pre-Q If 0, it will not automatically turn on. Q and newer it will only automatically
* turn on if the {@link #AUTOMATIC_POWER_SAVER_MODE} setting is also set to
@@ -12158,7 +12255,6 @@
*/
public static final String LOW_POWER_MODE_TRIGGER_LEVEL = "low_power_trigger_level";
-
private static final Validator LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR =
new SettingsValidators.InclusiveIntegerRangeValidator(0, 100);
@@ -13149,6 +13245,8 @@
ENCODED_SURROUND_OUTPUT,
ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
LOW_POWER_MODE_TRIGGER_LEVEL,
+ LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
+ LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
BLUETOOTH_ON,
PRIVATE_DNS_MODE,
PRIVATE_DNS_SPECIFIER,
@@ -13187,6 +13285,10 @@
VALIDATORS.put(ENCODED_SURROUND_OUTPUT, ENCODED_SURROUND_OUTPUT_VALIDATOR);
VALIDATORS.put(ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS_VALIDATOR);
+ VALIDATORS.put(LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
+ LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL_VALIDATOR);
+ VALIDATORS.put(LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
+ LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED_VALIDATOR);
VALIDATORS.put(LOW_POWER_MODE_TRIGGER_LEVEL, LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR);
VALIDATORS.put(LOW_POWER_MODE_TRIGGER_LEVEL_MAX,
LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR);
@@ -13701,6 +13803,19 @@
"user_preferred_sub2","user_preferred_sub3"};
/**
+ * Which subscription is enabled for a physical slot.
+ * @hide
+ */
+ public static final String ENABLED_SUBSCRIPTION_FOR_SLOT = "enabled_subscription_for_slot";
+
+ /**
+ * Whether corresponding logical modem is enabled for a physical slot.
+ * The value 1 - enable, 0 - disable
+ * @hide
+ */
+ public static final String MODEM_STACK_ENABLED_FOR_SLOT = "modem_stack_enabled_for_slot";
+
+ /**
* Whether to enable new contacts aggregator or not.
* The value 1 - enable, 0 - disable
* @hide
@@ -13917,11 +14032,12 @@
* The following keys are supported:
*
* <pre>
- * enabled (boolean)
- * requires_targeting_p (boolean)
- * max_squeeze_remeasure_attempts (int)
- * edit_choices_before_sending (boolean)
- * show_in_heads_up (boolean)
+ * enabled (boolean)
+ * requires_targeting_p (boolean)
+ * max_squeeze_remeasure_attempts (int)
+ * edit_choices_before_sending (boolean)
+ * show_in_heads_up (boolean)
+ * min_num_system_generated_replies (int)
* </pre>
* @see com.android.systemui.statusbar.policy.SmartReplyConstants
* @hide
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index aaba85b..8e0f522 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -67,7 +67,7 @@
private static final String TAG = AugmentedAutofillService.class.getSimpleName();
- // TODO(b/111330312): STOPSHIP use dynamic value, or change to false
+ // TODO(b/123100811): STOPSHIP use dynamic value, or change to false
static final boolean DEBUG = true;
static final boolean VERBOSE = false;
@@ -127,8 +127,6 @@
return false;
}
- // TODO(b/111330312): add methods to disable autofill per app / activity?
-
/**
* Asks the service to handle an "augmented" autofill request.
*
@@ -175,12 +173,11 @@
focusedValue, requestTime, callback);
mAutofillProxies.put(sessionId, proxy);
} else {
- // TODO(b/111330312): figure out if it's ok to reuse the proxy; add logging
- // TODO(b/111330312): also make sure to cover scenario on CTS test
+ // TODO(b/123099468): figure out if it's ok to reuse the proxy; add logging
if (DEBUG) Log.d(TAG, "Reusing proxy for session " + sessionId);
proxy.update(focusedId, focusedValue);
}
- // TODO(b/111330312): set cancellation signal
+ // TODO(b/123101711): set cancellation signal
final CancellationSignal cancellationSignal = null;
onFillRequest(new FillRequest(proxy), cancellationSignal, new FillController(proxy),
new FillCallback(proxy));
@@ -193,7 +190,7 @@
final int sessionId = mAutofillProxies.keyAt(i);
final AutofillProxy proxy = mAutofillProxies.valueAt(i);
if (proxy == null) {
- // TODO(b/111330312): this might be fine, in which case we should logv it
+ // TODO(b/123100811): this might be fine, in which case we should logv it
Log.w(TAG, "No proxy for session " + sessionId);
return;
}
@@ -303,7 +300,7 @@
this.mFocusedId = focusedId;
this.mFocusedValue = focusedValue;
this.mRequestTime = requestTime;
- // TODO(b/111330312): linkToDeath
+ // TODO(b/123099468): linkToDeath
}
@NonNull
@@ -366,7 +363,7 @@
private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue) {
synchronized (mLock) {
- // TODO(b/111330312): should we close the popupwindow if the focused id changed?
+ // TODO(b/123099468): should we close the popupwindow if the focused id changed?
mFocusedId = focusedId;
mFocusedValue = focusedValue;
}
@@ -425,7 +422,7 @@
default:
Slog.w(TAG, "invalid event reported: " + event);
}
- // TODO(b/111330312): log metrics as well
+ // TODO(b/122858578): log metrics as well
}
public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java
index bfb4aad..f2a7a35 100644
--- a/core/java/android/service/autofill/augmented/FillCallback.java
+++ b/core/java/android/service/autofill/augmented/FillCallback.java
@@ -59,7 +59,8 @@
if (fillWindow != null) {
fillWindow.show();
}
- // TODO(b/111330312): properly implement on server-side by updating the Session state
- // accordingly (and adding CTS tests)
+ // TODO(b/123099468): must notify the server so it can update the session state to avoid
+ // showing conflicting UIs (for example, if a new request is made to the main autofill
+ // service and it now wants to show something).
}
}
diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java
index dad5067..af9905f 100644
--- a/core/java/android/service/autofill/augmented/FillRequest.java
+++ b/core/java/android/service/autofill/augmented/FillRequest.java
@@ -29,7 +29,7 @@
* @hide
*/
@SystemApi
-// TODO(b/111330312): pass a requestId and/or sessionId
+// TODO(b/123100811): pass a requestId and/or sessionId?
@TestApi
// TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
// in the same package as the test, and that module is compiled with SDK=test_current
diff --git a/core/java/android/service/autofill/augmented/FillResponse.java b/core/java/android/service/autofill/augmented/FillResponse.java
index 5285132..f1e904a 100644
--- a/core/java/android/service/autofill/augmented/FillResponse.java
+++ b/core/java/android/service/autofill/augmented/FillResponse.java
@@ -19,8 +19,6 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.view.autofill.AutofillId;
import java.util.List;
@@ -34,7 +32,7 @@
@TestApi
//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
//in the same package as the test, and that module is compiled with SDK=test_current
-public final class FillResponse implements Parcelable {
+public final class FillResponse {
private final FillWindow mFillWindow;
@@ -70,8 +68,8 @@
* @return this builder
*/
public Builder setFillWindow(@NonNull FillWindow fillWindow) {
- // TODO(b/111330312): implement / check not null / unit test
- // TODO(b/111330312): throw exception if FillWindow not updated yet
+ // TODO(b/123100712): check not null / unit test / throw exception if FillWindow not
+ // updated yet
mFillWindow = fillWindow;
return this;
}
@@ -85,7 +83,7 @@
* @return this builder
*/
public Builder setIgnoredIds(@NonNull List<AutofillId> ids) {
- // TODO(b/111330312): implement / check not null / unit test
+ // TODO(b/123100695): implement / check not null / unit test
return this;
}
@@ -102,37 +100,10 @@
* @return A built response.
*/
public FillResponse build() {
- // TODO(b/111330312): check conditions / add unit test
+ // TODO(b/123100712): check conditions / add unit test
return new FillResponse(this);
}
-
- // TODO(b/111330312): add methods to disable app / activity, either here or on manager
}
- // TODO(b/111330312): implement to String
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- // TODO(b/111330312): implement
- }
-
- public static final Parcelable.Creator<FillResponse> CREATOR =
- new Parcelable.Creator<FillResponse>() {
-
- @Override
- public FillResponse createFromParcel(Parcel parcel) {
- // TODO(b/111330312): implement
- return null;
- }
-
- @Override
- public FillResponse[] newArray(int size) {
- return new FillResponse[size];
- }
- };
+ // TODO(b/123100811): implement to String
}
diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java
index 51b0f01..40e3a12 100644
--- a/core/java/android/service/autofill/augmented/FillWindow.java
+++ b/core/java/android/service/autofill/augmented/FillWindow.java
@@ -20,7 +20,6 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -42,8 +41,6 @@
import dalvik.system.CloseGuard;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
/**
* Handle to a window used to display the augmented autofill UI.
@@ -71,18 +68,6 @@
public final class FillWindow implements AutoCloseable {
private static final String TAG = "FillWindow";
- /** Indicates the data being shown is a physical address */
- public static final long FLAG_METADATA_ADDRESS = 0x1;
-
- // TODO(b/111330312): add more flags
-
- /** @hide */
- @LongDef(prefix = { "FLAG" }, value = {
- FLAG_METADATA_ADDRESS,
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface Flags{}
-
private final Object mLock = new Object();
private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -108,29 +93,22 @@
*
* @param rootView new root view
* @param area coordinates to render the view.
- * @param flags optional flags such as metadata of what will be rendered in the window. The
- * Smart Suggestion host might decide whether or not to render the UI based on them.
+ * @param flags currently not used.
*
* @return boolean whether the window was updated or not.
*
* @throws IllegalArgumentException if the area is not compatible with this window
*/
- public boolean update(@NonNull Area area, @NonNull View rootView, @Flags long flags) {
+ public boolean update(@NonNull Area area, @NonNull View rootView, long flags) {
if (DEBUG) {
Log.d(TAG, "Updating " + area + " + with " + rootView);
}
- // TODO(b/111330312): add test case for null
+ // TODO(b/123100712): add test case for null
Preconditions.checkNotNull(area);
Preconditions.checkNotNull(rootView);
- // TODO(b/111330312): must check the area is a valid object returned by
+ // TODO(b/123100712): must check the area is a valid object returned by
// SmartSuggestionParams, throw IAE if not
- // TODO(b/111330312): must some how pass metadata to the SmartSuggestiongs provider
-
-
- // TODO(b/111330312): use a SurfaceControl approach; for now, we're manually creating
- // the window underneath the existing view.
-
final PresentationParams smartSuggestion = area.proxy.getSmartSuggestionParams();
if (smartSuggestion == null) {
Log.w(TAG, "No SmartSuggestionParams");
@@ -148,12 +126,12 @@
mProxy = area.proxy;
- // TODO(b/111330312): once we have the SurfaceControl approach, we should update the
+ // TODO(b/123227534): once we have the SurfaceControl approach, we should update the
// window instead of destroying. In fact, it might be better to allocate a full window
// initially, which is transparent (and let touches get through) everywhere but in the
// rect boundaries.
- // TODO(b/111330312): make sure all touch events are handled, window is always closed,
+ // TODO(b/123099468): make sure all touch events are handled, window is always closed,
// etc.
mWm = rootView.getContext().getSystemService(WindowManager.class);
@@ -181,7 +159,7 @@
/** @hide */
void show() {
- // TODO(b/111330312): check if updated first / throw exception
+ // TODO(b/123100712): check if updated first / throw exception
if (DEBUG) Log.d(TAG, "show()");
synchronized (mLock) {
checkNotDestroyedLocked();
diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl
index dac7590..2b072664 100644
--- a/core/java/android/service/autofill/augmented/IFillCallback.aidl
+++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl
@@ -24,8 +24,7 @@
* @hide
*/
interface IFillCallback {
- // TODO(b/111330312): add cancellation (after we have CTS tests, so we can test it)
+ // TODO(b/123101711): add cancellation (after we have CTS tests, so we can test it)
// void onCancellable(in ICancellationSignal cancellation);
- // TODO(b/111330312): might need to pass the response (once IME implements Smart Suggestions)
void onSuccess();
}
diff --git a/core/java/android/service/autofill/augmented/PresentationParams.java b/core/java/android/service/autofill/augmented/PresentationParams.java
index b60064e..1fb9032 100644
--- a/core/java/android/service/autofill/augmented/PresentationParams.java
+++ b/core/java/android/service/autofill/augmented/PresentationParams.java
@@ -190,7 +190,7 @@
*/
@Nullable
public Area getSubArea(@NonNull Rect bounds) {
- // TODO(b/111330312): implement / check boundaries / throw IAE / add unit test
+ // TODO(b/123100712): implement / check boundaries / throw IAE / add unit test
return null;
}
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 302e1a6..020de7f 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -116,6 +116,13 @@
mHandler.sendMessage(obtainMessage(ContentCaptureService::handleFinishSession,
ContentCaptureService.this, sessionId));
}
+
+ @Override
+ public void onUserDataRemovalRequest(UserDataRemovalRequest request) {
+ mHandler.sendMessage(
+ obtainMessage(ContentCaptureService::handleOnUserDataRemovalRequest,
+ ContentCaptureService.this, request));
+ }
};
/**
@@ -431,6 +438,10 @@
onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId));
}
+ private void handleOnUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
+ onUserDataRemovalRequest(request);
+ }
+
/**
* Checks if the given {@code uid} owns the session associated with the event.
*/
diff --git a/core/java/android/service/contentcapture/IContentCaptureService.aidl b/core/java/android/service/contentcapture/IContentCaptureService.aidl
index a8dd213..d92fb3b 100644
--- a/core/java/android/service/contentcapture/IContentCaptureService.aidl
+++ b/core/java/android/service/contentcapture/IContentCaptureService.aidl
@@ -19,6 +19,7 @@
import android.os.IBinder;
import android.service.contentcapture.SnapshotData;
import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.UserDataRemovalRequest;
import com.android.internal.os.IResultReceiver;
@@ -36,4 +37,5 @@
in IResultReceiver clientReceiver);
void onSessionFinished(String sessionId);
void onActivitySnapshot(String sessionId, in SnapshotData snapshotData);
+ void onUserDataRemovalRequest(in UserDataRemovalRequest request);
}
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index 4dc10cd..ffb524d 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -103,10 +103,23 @@
*/
public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS =
"android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
+
/** @see android.telephony.euicc.EuiccManager#ACTION_PROVISION_EMBEDDED_SUBSCRIPTION */
public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION =
"android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+ /** @see android.telephony.euicc.EuiccManager#ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED */
+ public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED =
+ "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+
+ /** @see android.telephony.euicc.EuiccManager#ACTION_DELETE_SUBSCRIPTION_PRIVILEGED */
+ public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED =
+ "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
+
+ /** @see android.telephony.euicc.EuiccManager#ACTION_RENAME_SUBSCRIPTION_PRIVILEGED */
+ public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED =
+ "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
+
// LUI resolution actions. These are called by the platform to resolve errors in situations that
// require user interaction.
// TODO(b/33075886): Define extras for any input parameters to these dialogs once they are
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index de532b7..b6788f5 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -15,8 +15,6 @@
*/
package android.service.notification;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
import android.app.Notification;
import android.os.Bundle;
import android.os.Parcel;
@@ -24,10 +22,7 @@
/**
* Ranking updates from the Assistant.
- * @hide
*/
-@SystemApi
-@TestApi
public final class Adjustment implements Parcelable {
private final String mPackage;
private final String mKey;
@@ -39,6 +34,7 @@
* Data type: ArrayList of {@code String}, where each is a representation of a
* {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
* See {@link android.app.Notification.Builder#addPerson(String)}.
+ * @hide
*/
public static final String KEY_PEOPLE = "key_people";
/**
@@ -46,6 +42,7 @@
* users. If a user chooses to snooze a notification until one of these criterion, the
* assistant will be notified via
* {@link NotificationAssistantService#onNotificationSnoozedUntilContext}.
+ * @hide
*/
public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
/**
@@ -112,7 +109,7 @@
mUser = user;
}
- protected Adjustment(Parcel in) {
+ private Adjustment(Parcel in) {
if (in.readInt() == 1) {
mPackage = in.readString();
} else {
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index ad34ab3..e93b158 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -21,8 +21,6 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.admin.DevicePolicyManager;
@@ -61,11 +59,7 @@
* <p>
* All callbacks are called on the main thread.
* </p>
- *
- * @hide
*/
-@SystemApi
-@TestApi
public abstract class NotificationAssistantService extends NotificationListenerService {
private static final String TAG = "NotificationAssistants";
@@ -109,6 +103,7 @@
*
* @param sbn the notification to snooze
* @param snoozeCriterionId the {@link SnoozeCriterion#getId()} representing a device context.
+ * @hide
*/
abstract public void onNotificationSnoozedUntilContext(StatusBarNotification sbn,
String snoozeCriterionId);
@@ -250,6 +245,7 @@
* {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
* notification.
* @param key The key of the notification to snooze
+ * @hide
*/
public final void unsnoozeNotification(String key) {
if (!isBound()) return;
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 0e63cd3..c734b63 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1617,14 +1617,16 @@
}
/**
- * @hide
+ * Returns a list of smart {@link Notification.Action} that can be added by the
+ * {@link NotificationAssistantService}
*/
public List<Notification.Action> getSmartActions() {
return mSmartActions;
}
/**
- * @hide
+ * Returns a list of smart replies that can be added by the
+ * {@link NotificationAssistantService}
*/
public List<CharSequence> getSmartReplies() {
return mSmartReplies;
diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java
index e5f3dfb..814b477 100644
--- a/core/java/android/service/notification/NotificationStats.java
+++ b/core/java/android/service/notification/NotificationStats.java
@@ -16,8 +16,6 @@
package android.service.notification;
import android.annotation.IntDef;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
import android.app.RemoteInput;
import android.os.Parcel;
import android.os.Parcelable;
@@ -25,11 +23,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-/**
- * @hide
- */
-@TestApi
-@SystemApi
public final class NotificationStats implements Parcelable {
private boolean mSeen;
@@ -105,7 +98,7 @@
public NotificationStats() {
}
- protected NotificationStats(Parcel in) {
+ private NotificationStats(Parcel in) {
mSeen = in.readByte() != 0;
mExpanded = in.readByte() != 0;
mDirectReplied = in.readByte() != 0;
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index e105fdf..2789651 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -16,6 +16,7 @@
package android.service.voice;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -40,6 +41,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -77,6 +80,33 @@
*/
public static final String SERVICE_META_DATA = "android.voice_interaction";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"VOICE_STATE_"}, value = {
+ VOICE_STATE_NONE,
+ VOICE_STATE_CONDITIONAL_LISTENING,
+ VOICE_STATE_LISTENING,
+ VOICE_STATE_FULFILLING})
+ public @interface VoiceState {
+ }
+
+ /**
+ * Voice assistant inactive.
+ */
+ public static final int VOICE_STATE_NONE = 0;
+ /**
+ * Voice assistant listening, but will only trigger if it hears a request it can fulfill.
+ */
+ public static final int VOICE_STATE_CONDITIONAL_LISTENING = 1;
+ /**
+ * Voice assistant is listening to user speech.
+ */
+ public static final int VOICE_STATE_LISTENING = 2;
+ /**
+ * Voice assistant is fulfilling an action requested by the user.
+ */
+ public static final int VOICE_STATE_FULFILLING = 3;
+
IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() {
@Override
public void ready() {
@@ -341,6 +371,43 @@
}
}
+ /**
+ * Requests that the voice state UI indicate the given state.
+ *
+ * @param state value indicating whether the assistant is listening, fulfilling, etc.
+ */
+ public final void setVoiceState(@VoiceState int state) {
+ try {
+ mSystemService.setVoiceState(state);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Displays the given voice transcription contents.
+ */
+ public final void setTranscription(@NonNull String transcription) {
+ try {
+ mSystemService.setTranscription(transcription);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Hides transcription.
+ *
+ * @param immediate if {@code true}, remove before transcription animation completes.
+ */
+ public final void clearTranscription(boolean immediate) {
+ try {
+ mSystemService.clearTranscription(immediate);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("VOICE INTERACTION");
diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl
index f7acfc5..b0269e3 100644
--- a/core/java/android/service/vr/IVrManager.aidl
+++ b/core/java/android/service/vr/IVrManager.aidl
@@ -110,13 +110,5 @@
* @param standy True if the device is entering standby, false if it's exiting standby.
*/
void setStandbyEnabled(boolean standby);
-
- /**
- * Start VR Input method for the given packageName in {@param componentName}.
- * This method notifies InputMethodManagerService to use VR IME instead of
- * regular phone IME.
- */
- void setVrInputMethod(in ComponentName componentName);
-
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0d7223d..b197c8a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -784,8 +784,11 @@
// only internal implementations like ImageWallpaper
DisplayInfo displayInfo = new DisplayInfo();
mDisplay.getDisplayInfo(displayInfo);
- mLayout.width = Math.max(displayInfo.logicalWidth, myWidth);
- mLayout.height = Math.max(displayInfo.logicalHeight, myHeight);
+ final float layoutScale = Math.max(
+ (float) displayInfo.logicalHeight / (float) myHeight,
+ (float) displayInfo.logicalWidth / (float) myWidth);
+ mLayout.height = (int) (myHeight * layoutScale);
+ mLayout.width = (int) (myWidth * layoutScale);
mWindowFlags |= WindowManager.LayoutParams.FLAG_SCALED;
}
diff --git a/core/java/android/util/DocumentsStatsLog.java b/core/java/android/util/DocumentsStatsLog.java
new file mode 100644
index 0000000..f483944
--- /dev/null
+++ b/core/java/android/util/DocumentsStatsLog.java
@@ -0,0 +1,168 @@
+/*
+ * 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.util;
+
+import android.annotation.SystemApi;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsProvider;
+
+/**
+ * DocumentsStatsLog provides APIs to send DocumentsUI related events to statsd.
+ * @hide
+ */
+@SystemApi
+public class DocumentsStatsLog {
+
+ private DocumentsStatsLog() {}
+
+ /**
+ * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up.
+ *
+ * @param action action that launches DocumentsUI.
+ * @param hasInitialUri is DocumentsUI launched with
+ * {@link DocumentsContract#EXTRA_INITIAL_URI}.
+ * @param mimeType the requested mime type.
+ * @param rootUri the resolved rootUri, or {@code null} if the provider doesn't
+ * support {@link DocumentsProvider#findDocumentPath(String, String)}
+ */
+ public static void logActivityLaunch(
+ int action, boolean hasInitialUri, int mimeType, int rootUri) {
+ StatsLog.write(StatsLog.DOCS_UI_LAUNCH_REPORTED, action, hasInitialUri, mimeType, rootUri);
+ }
+
+ /**
+ * Logs root visited event.
+ *
+ * @param scope whether it's in FILES or PICKER mode.
+ * @param root the root that user visited
+ */
+ public static void logRootVisited(int scope, int root) {
+ StatsLog.write(StatsLog.DOCS_UI_ROOT_VISITED, scope, root);
+ }
+
+ /**
+ * Logs file operation stats. Call this when a file operation has completed.
+ *
+ * @param provider whether it's system or external provider
+ * @param fileOp the file operation
+ */
+ public static void logFileOperation(int provider, int fileOp) {
+ StatsLog.write(StatsLog.DOCS_UI_PROVIDER_FILE_OP, provider, fileOp);
+ }
+
+ /**
+ * Logs file operation stats. Call this when a copy/move operation has completed with a specific
+ * mode.
+ *
+ * @param fileOp copy or move file operation
+ * @param mode the mode for copy and move operation
+ */
+ public static void logFileOperationCopyMoveMode(int fileOp, int mode) {
+ StatsLog.write(StatsLog.DOCS_UI_FILE_OP_COPY_MOVE_MODE_REPORTED, fileOp, mode);
+ }
+
+ /**
+ * Logs file sub operation stats. Call this when a file operation has failed.
+ *
+ * @param authority the authority of the source document
+ * @param subOp the sub-file operation
+ */
+ public static void logFileOperationFailure(int authority, int subOp) {
+ StatsLog.write(StatsLog.DOCS_UI_FILE_OP_FAILURE, authority, subOp);
+ }
+
+ /**
+ * Logs the cancellation of a file operation. Call this when a job is canceled
+ *
+ * @param fileOp the file operation.
+ */
+ public static void logFileOperationCanceled(int fileOp) {
+ StatsLog.write(StatsLog.DOCS_UI_FILE_OP_CANCELED, fileOp);
+ }
+
+ /**
+ * Logs startup time in milliseconds.
+ *
+ * @param startupMs
+ */
+ public static void logStartupMs(int startupMs) {
+ StatsLog.write(StatsLog.DOCS_UI_STARTUP_MS, startupMs);
+ }
+
+ /**
+ * Logs the action that was started by user.
+ *
+ * @param userAction
+ */
+ public static void logUserAction(int userAction) {
+ StatsLog.write(StatsLog.DOCS_UI_USER_ACTION_REPORTED, userAction);
+ }
+
+ /**
+ * Logs the invalid type when invalid scoped access is requested.
+ *
+ * @param type the type of invalid scoped access request.
+ */
+ public static void logInvalidScopedAccessRequest(int type) {
+ StatsLog.write(StatsLog.DOCS_UI_INVALID_SCOPED_ACCESS_REQUEST, type);
+ }
+
+ /**
+ * Logs the package name that launches docsui picker mode.
+ *
+ * @param packageName
+ */
+ public static void logPickerLaunchedFrom(String packageName) {
+ StatsLog.write(StatsLog.DOCS_UI_PICKER_LAUNCHED_FROM_REPORTED, packageName);
+ }
+
+ /**
+ * Logs the search type.
+ *
+ * @param searchType
+ */
+ public static void logSearchType(int searchType) {
+ StatsLog.write(StatsLog.DOCS_UI_SEARCH_TYPE_REPORTED, searchType);
+ }
+
+ /**
+ * Logs the search mode.
+ *
+ * @param searchMode
+ */
+ public static void logSearchMode(int searchMode) {
+ StatsLog.write(StatsLog.DOCS_UI_SEARCH_MODE_REPORTED, searchMode);
+ }
+
+ /**
+ * Logs the pick result information.
+ *
+ * @param actionCount total user action count during pick process.
+ * @param duration total time spent on pick process.
+ * @param fileCount number of picked files.
+ * @param isSearching are the picked files found by search.
+ * @param root the root where the picked files located.
+ * @param mimeType the mime type of the picked file. Only for single-select case.
+ * @param repeatedlyPickTimes number of times that the file has been picked before. Only for
+ * single-select case.
+ */
+ public static void logFilePick(int actionCount, long duration, int fileCount,
+ boolean isSearching, int root, int mimeType, int repeatedlyPickTimes) {
+ StatsLog.write(StatsLog.DOCS_UI_PICK_RESULT_REPORTED, actionCount, duration, fileCount,
+ isSearching, root, mimeType, repeatedlyPickTimes);
+ }
+}
diff --git a/core/java/android/util/LongArrayQueue.java b/core/java/android/util/LongArrayQueue.java
new file mode 100644
index 0000000..d5f0484
--- /dev/null
+++ b/core/java/android/util/LongArrayQueue.java
@@ -0,0 +1,165 @@
+/*
+ * 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.util;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
+
+import java.util.NoSuchElementException;
+
+/**
+ * A lightweight implementation for a queue with long values.
+ * Additionally supports getting an element with a specified position from the head of the queue.
+ * The queue grows in size if needed to accommodate new elements.
+ *
+ * @hide
+ */
+public class LongArrayQueue {
+
+ private long[] mValues;
+ private int mSize;
+ private int mHead;
+ private int mTail;
+
+ /**
+ * Initializes a queue with the given starting capacity.
+ *
+ * @param initialCapacity the capacity.
+ */
+ public LongArrayQueue(int initialCapacity) {
+ if (initialCapacity == 0) {
+ mValues = EmptyArray.LONG;
+ } else {
+ mValues = ArrayUtils.newUnpaddedLongArray(initialCapacity);
+ }
+ mSize = 0;
+ mHead = mTail = 0;
+ }
+
+ /**
+ * Initializes a queue with default starting capacity.
+ */
+ public LongArrayQueue() {
+ this(16);
+ }
+
+ private void grow() {
+ if (mSize < mValues.length) {
+ throw new IllegalStateException("Queue not full yet!");
+ }
+ final int newSize = GrowingArrayUtils.growSize(mSize);
+ final long[] newArray = ArrayUtils.newUnpaddedLongArray(newSize);
+ final int r = mValues.length - mHead; // Number of elements on and to the right of head.
+ System.arraycopy(mValues, mHead, newArray, 0, r);
+ System.arraycopy(mValues, 0, newArray, r, mHead);
+ mValues = newArray;
+ mHead = 0;
+ mTail = mSize;
+ }
+
+ /**
+ * Returns the number of elements in the queue.
+ */
+ public int size() {
+ return mSize;
+ }
+
+ /**
+ * Removes all elements from this queue.
+ */
+ public void clear() {
+ mSize = 0;
+ mHead = mTail = 0;
+ }
+
+ /**
+ * Adds a value to the tail of the queue.
+ *
+ * @param value the value to be added.
+ */
+ public void addLast(long value) {
+ if (mSize == mValues.length) {
+ grow();
+ }
+ mValues[mTail] = value;
+ mTail = (mTail + 1) % mValues.length;
+ mSize++;
+ }
+
+ /**
+ * Removes an element from the head of the queue.
+ *
+ * @return the element at the head of the queue.
+ * @throws NoSuchElementException if the queue is empty.
+ */
+ public long removeFirst() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ final long ret = mValues[mHead];
+ mHead = (mHead + 1) % mValues.length;
+ mSize--;
+ return ret;
+ }
+
+ /**
+ * Returns the element at the given position from the head of the queue, where 0 represents the
+ * head of the queue.
+ *
+ * @param position the position from the head of the queue.
+ * @return the element found at the given position.
+ * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
+ * {@code position} >= {@link #size()}
+ */
+ public long get(int position) {
+ if (position < 0 || position >= mSize) {
+ throw new IndexOutOfBoundsException("Index " + position
+ + " not valid for a queue of size " + mSize);
+ }
+ final int index = (mHead + position) % mValues.length;
+ return mValues[index];
+ }
+
+ /**
+ * Returns the element at the head of the queue, without removing it.
+ *
+ * @return the element at the head of the queue.
+ * @throws NoSuchElementException if the queue is empty
+ */
+ public long peekFirst() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ return mValues[mHead];
+ }
+
+ /**
+ * Returns the element at the tail of the queue.
+ *
+ * @return the element at the tail of the queue.
+ * @throws NoSuchElementException if the queue is empty.
+ */
+ public long peekLast() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
+ return mValues[index];
+ }
+}
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 33b3ff4f..7d9ec70 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -237,6 +237,16 @@
private static final int LONG_PRESS = 2;
private static final int TAP = 3;
+ /**
+ * If a MotionEvent has CLASSIFICATION_AMBIGUOUS_GESTURE set, then certain actions, such as
+ * scrolling, will be inhibited. However, to account for the possibility of incorrect
+ * classification, the default scrolling will only be inhibited if the gesture moves beyond
+ * (default touch slop * AMBIGUOUS_GESTURE_MULTIPLIER). Likewise, the default long press
+ * timeout will be increased for some situations where the default behaviour
+ * is to cancel it.
+ */
+ private static final int AMBIGUOUS_GESTURE_MULTIPLIER = 2;
+
private final Handler mHandler;
@UnsupportedAppUsage
private final OnGestureListener mListener;
@@ -292,27 +302,27 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case SHOW_PRESS:
- mListener.onShowPress(mCurrentDownEvent);
- break;
-
- case LONG_PRESS:
- dispatchLongPress();
- break;
-
- case TAP:
- // If the user's finger is still down, do not count it as a tap
- if (mDoubleTapListener != null) {
- if (!mStillDown) {
- mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
- } else {
- mDeferConfirmSingleTap = true;
- }
- }
- break;
+ case SHOW_PRESS:
+ mListener.onShowPress(mCurrentDownEvent);
+ break;
- default:
- throw new RuntimeException("Unknown message " + msg); //never
+ case LONG_PRESS:
+ dispatchLongPress();
+ break;
+
+ case TAP:
+ // If the user's finger is still down, do not count it as a tap
+ if (mDoubleTapListener != null) {
+ if (!mStillDown) {
+ mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
+ } else {
+ mDeferConfirmSingleTap = true;
+ }
+ }
+ break;
+
+ default:
+ throw new RuntimeException("Unknown message " + msg); //never
}
}
}
@@ -427,7 +437,7 @@
if (context == null) {
//noinspection deprecation
touchSlop = ViewConfiguration.getTouchSlop();
- doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this
+ doubleTapTouchSlop = touchSlop; // Hack rather than adding a hidden method for this
doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
//noinspection deprecation
mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
@@ -605,6 +615,10 @@
if (mInLongPress || mInContextClick) {
break;
}
+
+ final int motionClassification = ev.getClassification();
+ final boolean hasPendingLongPress = mHandler.hasMessages(LONG_PRESS);
+
final float scrollX = mLastFocusX - focusX;
final float scrollY = mLastFocusY - focusY;
if (mIsDoubleTapping) {
@@ -615,6 +629,31 @@
final int deltaY = (int) (focusY - mDownFocusY);
int distance = (deltaX * deltaX) + (deltaY * deltaY);
int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
+
+ final boolean ambiguousGesture =
+ motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
+ final boolean shouldInhibitDefaultAction =
+ hasPendingLongPress && ambiguousGesture;
+ if (shouldInhibitDefaultAction) {
+ // Inhibit default long press
+ if (distance > slopSquare) {
+ // The default action here is to remove long press. But if the touch
+ // slop below gets increased, and we never exceed the modified touch
+ // slop while still receiving AMBIGUOUS_GESTURE, we risk that *nothing*
+ // will happen in response to user input. To prevent this,
+ // reschedule long press with a modified timeout.
+ mHandler.removeMessages(LONG_PRESS);
+ final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
+ mHandler.sendEmptyMessageAtTime(LONG_PRESS, ev.getDownTime()
+ + longPressTimeout * AMBIGUOUS_GESTURE_MULTIPLIER);
+ }
+ // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll
+ // until the gesture is resolved.
+ // However, for safety, simply increase the touch slop in case the
+ // classification is erroneous. Since the value is squared, multiply twice.
+ slopSquare *= AMBIGUOUS_GESTURE_MULTIPLIER * AMBIGUOUS_GESTURE_MULTIPLIER;
+ }
+
if (distance > slopSquare) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
@@ -633,6 +672,12 @@
mLastFocusX = focusX;
mLastFocusY = focusY;
}
+ final boolean deepPress =
+ motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
+ if (deepPress && hasPendingLongPress) {
+ mHandler.removeMessages(LONG_PRESS);
+ mHandler.sendEmptyMessage(LONG_PRESS);
+ }
break;
case MotionEvent.ACTION_UP:
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 330d72f..42ac880 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -51,6 +51,7 @@
import android.view.AppTransitionAnimationSpec;
import android.view.WindowContentFrameStats;
import android.view.WindowManager;
+import android.view.SurfaceControl;
/**
* System private interface to the window manager.
@@ -555,8 +556,8 @@
* display content info to any SurfaceControl, as this would be a security issue.
*
* @param displayId The id of the display.
- * @param surfaceControlHandle The SurfaceControl handle that the top level layers for the
+ * @param surfaceControlHandle The SurfaceControl that the top level layers for the
* display should be re-parented to.
*/
- void reparentDisplayContent(int displayId, in IBinder surfaceControlHandle);
+ void reparentDisplayContent(int displayId, in SurfaceControl sc);
}
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index ce71b07..7291d0b 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -122,7 +122,8 @@
mController.scheduleApplyChangeInsets();
}
- void applyChangeInsets(InsetsState state) {
+ @VisibleForTesting
+ public void applyChangeInsets(InsetsState state) {
final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
ArrayList<SurfaceParams> params = new ArrayList<>();
if (offset.left != 0) {
@@ -161,8 +162,9 @@
private Insets getInsetsFromState(InsetsState state, Rect frame,
@Nullable @InsetSide SparseIntArray typeSideMap) {
return state.calculateInsets(frame, false /* isScreenRound */,
- false /* alwaysConsumerNavBar */, null /* displayCutout */, typeSideMap)
- .getInsets(mTypes);
+ false /* alwaysConsumerNavBar */, null /* displayCutout */,
+ null /* legacyContentInsets */, null /* legacyStableInsets */, typeSideMap)
+ .getInsets(mTypes);
}
private Insets sanitize(Insets insets) {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index c2ade76..dd6231d 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -56,6 +56,9 @@
private final Runnable mAnimCallback;
+ private final Rect mLastLegacyContentInsets = new Rect();
+ private final Rect mLastLegacyStableInsets = new Rect();
+
public InsetsController(ViewRootImpl viewRoot) {
mViewRoot = viewRoot;
mAnimCallback = () -> {
@@ -70,6 +73,7 @@
}
WindowInsets insets = state.calculateInsets(mFrame, mLastInsets.isRound(),
mLastInsets.shouldAlwaysConsumeNavBar(), mLastInsets.getDisplayCutout(),
+ mLastLegacyContentInsets, mLastLegacyStableInsets,
null /* typeSideMap */);
mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets);
};
@@ -102,8 +106,12 @@
*/
@VisibleForTesting
public WindowInsets calculateInsets(boolean isScreenRound,
- boolean alwaysConsumeNavBar, DisplayCutout cutout) {
+ boolean alwaysConsumeNavBar, DisplayCutout cutout, Rect legacyContentInsets,
+ Rect legacyStableInsets) {
+ mLastLegacyContentInsets.set(legacyContentInsets);
+ mLastLegacyStableInsets.set(legacyStableInsets);
mLastInsets = mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout,
+ legacyContentInsets, legacyStableInsets,
null /* typeSideMap */);
return mLastInsets;
}
@@ -223,15 +231,18 @@
mState.dump(prefix + " ", pw);
}
- void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
+ @VisibleForTesting
+ public void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation);
}
- void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
+ @VisibleForTesting
+ public void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
}
- void scheduleApplyChangeInsets() {
+ @VisibleForTesting
+ public void scheduleApplyChangeInsets() {
if (!mAnimCallbackScheduled) {
mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION,
mAnimCallback, null /* token*/);
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index cf8c0707..529776e 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
import static android.view.WindowInsets.Type.indexOf;
import android.annotation.IntDef;
@@ -119,11 +120,17 @@
*/
public WindowInsets calculateInsets(Rect frame, boolean isScreenRound,
boolean alwaysConsumeNavBar, DisplayCutout cutout,
+ @Nullable Rect legacyContentInsets, @Nullable Rect legacyStableInsets,
@Nullable @InsetSide SparseIntArray typeSideMap) {
Insets[] typeInsetsMap = new Insets[Type.SIZE];
Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
final Rect relativeFrame = new Rect(frame);
final Rect relativeFrameMax = new Rect(frame);
+ if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_IME
+ && legacyContentInsets != null && legacyStableInsets != null) {
+ WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets);
+ WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets);
+ }
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
InsetsSource source = mSources.get(type);
if (source == null) {
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index 567b279..1d2cf4b 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -24,6 +24,8 @@
import static android.view.RemoteAnimationTargetProto.POSITION;
import static android.view.RemoteAnimationTargetProto.PREFIX_ORDER_INDEX;
import static android.view.RemoteAnimationTargetProto.SOURCE_CONTAINER_BOUNDS;
+import static android.view.RemoteAnimationTargetProto.START_BOUNDS;
+import static android.view.RemoteAnimationTargetProto.START_LEASH;
import static android.view.RemoteAnimationTargetProto.TASK_ID;
import static android.view.RemoteAnimationTargetProto.WINDOW_CONFIGURATION;
@@ -57,9 +59,15 @@
*/
public static final int MODE_CLOSING = 1;
+ /**
+ * The app is in the set of resizing apps (eg. mode change) of this transition.
+ */
+ public static final int MODE_CHANGING = 2;
+
@IntDef(prefix = { "MODE_" }, value = {
MODE_OPENING,
- MODE_CLOSING
+ MODE_CLOSING,
+ MODE_CHANGING
})
@Retention(RetentionPolicy.SOURCE)
public @interface Mode {}
@@ -83,6 +91,13 @@
public final SurfaceControl leash;
/**
+ * The {@link SurfaceControl} for the starting state of a target if this transition is
+ * MODE_CHANGING, {@code null)} otherwise. This is relative to the app window.
+ */
+ @UnsupportedAppUsage
+ public final SurfaceControl startLeash;
+
+ /**
* Whether the app is translucent and may reveal apps behind.
*/
@UnsupportedAppUsage
@@ -128,6 +143,15 @@
public final Rect sourceContainerBounds;
/**
+ * The starting bounds of the source container in screen space coordinates. This is {@code null}
+ * if the animation target isn't MODE_CHANGING. Since this is the starting bounds, it's size
+ * should be equivalent to the size of the starting thumbnail. Note that sourceContainerBounds
+ * is the end bounds of a change transition.
+ */
+ @UnsupportedAppUsage
+ public final Rect startBounds;
+
+ /**
* The window configuration for the target.
*/
@UnsupportedAppUsage
@@ -141,7 +165,8 @@
public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
- Rect sourceContainerBounds, WindowConfiguration windowConfig, boolean isNotInRecents) {
+ Rect sourceContainerBounds, WindowConfiguration windowConfig, boolean isNotInRecents,
+ SurfaceControl startLeash, Rect startBounds) {
this.mode = mode;
this.taskId = taskId;
this.leash = leash;
@@ -153,6 +178,8 @@
this.sourceContainerBounds = new Rect(sourceContainerBounds);
this.windowConfiguration = windowConfig;
this.isNotInRecents = isNotInRecents;
+ this.startLeash = startLeash;
+ this.startBounds = startBounds == null ? null : new Rect(startBounds);
}
public RemoteAnimationTarget(Parcel in) {
@@ -167,6 +194,8 @@
sourceContainerBounds = in.readParcelable(null);
windowConfiguration = in.readParcelable(null);
isNotInRecents = in.readBoolean();
+ startLeash = in.readParcelable(null);
+ startBounds = in.readParcelable(null);
}
@Override
@@ -187,6 +216,8 @@
dest.writeParcelable(sourceContainerBounds, 0 /* flags */);
dest.writeParcelable(windowConfiguration, 0 /* flags */);
dest.writeBoolean(isNotInRecents);
+ dest.writeParcelable(startLeash, 0 /* flags */);
+ dest.writeParcelable(startBounds, 0 /* flags */);
}
public void dump(PrintWriter pw, String prefix) {
@@ -215,6 +246,8 @@
position.writeToProto(proto, POSITION);
sourceContainerBounds.writeToProto(proto, SOURCE_CONTAINER_BOUNDS);
windowConfiguration.writeToProto(proto, WINDOW_CONFIGURATION);
+ startLeash.writeToProto(proto, START_LEASH);
+ startBounds.writeToProto(proto, START_BOUNDS);
proto.end(token);
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index f3cb376..7fcb2af 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -185,6 +185,18 @@
}
/**
+ * Create a Surface assosciated with a given {@link SurfaceControl}. Buffers submitted to this
+ * surface will be displayed by the system compositor according to the parameters
+ * specified by the control. Multiple surfaces may be constructed from one SurfaceControl,
+ * but only one can be connected (e.g. have an active EGL context) at a time.
+ *
+ * @param from The SurfaceControl to assosciate this Surface with
+ */
+ public Surface(SurfaceControl from) {
+ copyFrom(from);
+ }
+
+ /**
* Create Surface from a {@link SurfaceTexture}.
*
* Images drawn to the Surface will be made available to the {@link
@@ -494,7 +506,6 @@
* in to it.
*
* @param other {@link SurfaceControl} to copy from.
- *
* @hide
*/
@UnsupportedAppUsage
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 5e98236..863b717 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -27,6 +27,10 @@
import static android.view.SurfaceControlProto.HASH_CODE;
import static android.view.SurfaceControlProto.NAME;
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
@@ -58,10 +62,16 @@
import java.io.Closeable;
/**
- * SurfaceControl
- * @hide
+ * Handle to an on-screen Surface managed by the system compositor. The SurfaceControl is
+ * a combination of a buffer source, and metadata about how to display the buffers.
+ * By constructing a {@link Surface} from this SurfaceControl you can submit buffers to be
+ * composited. Using {@link SurfaceControl.Transaction} you can manipulate various
+ * properties of how the buffer will be displayed on-screen. SurfaceControl's are
+ * arranged into a scene-graph like hierarchy, and as such any SurfaceControl may have
+ * a parent. Geometric properties like transform, crop, and Z-ordering will be inherited
+ * from the parent, as if the child were content in the parents buffer stream.
*/
-public class SurfaceControl implements Parcelable {
+public final class SurfaceControl implements Parcelable {
private static final String TAG = "SurfaceControl";
private static native long nativeCreate(SurfaceSession session, String name,
@@ -103,6 +113,8 @@
float dtdy, float dsdy);
private static native void nativeSetColorTransform(long transactionObj, long nativeObject,
float[] matrix, float[] translation);
+ private static native void nativeSetGeometry(long transactionObj, long nativeObject,
+ Rect sourceCrop, Rect dest, long orientation);
private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color);
private static native void nativeSetFlags(long transactionObj, long nativeObject,
int flags, int mask);
@@ -156,7 +168,7 @@
private static native void nativeReparentChildren(long transactionObj, long nativeObject,
IBinder handle);
private static native void nativeReparent(long transactionObj, long nativeObject,
- IBinder parentHandle);
+ long newParentNativeObject);
private static native void nativeSeverChildren(long transactionObj, long nativeObject);
private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject,
int scalingMode);
@@ -331,8 +343,7 @@
*/
public static final int BUILT_IN_DISPLAY_ID_HDMI = 1;
- /* Display power modes * /
-
+ // Display power modes.
/**
* Display power mode off: used while blanking the screen.
* Use only with {@link SurfaceControl#setDisplayPowerMode}.
@@ -403,7 +414,6 @@
/**
* Builder class for {@link SurfaceControl} objects.
- * @hide
*/
public static class Builder {
private SurfaceSession mSession;
@@ -427,8 +437,14 @@
}
/**
- * Construct a new {@link SurfaceControl} with the set parameters.
- * @hide
+ * Begin building a SurfaceControl.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Construct a new {@link SurfaceControl} with the set parameters. The builder
+ * remains valid.
*/
public SurfaceControl build() {
if (mWidth < 0 || mHeight < 0) {
@@ -447,7 +463,6 @@
* Set a debugging-name for the SurfaceControl.
*
* @param name A name to identify the Surface in debugging.
- * @hide
*/
public Builder setName(String name) {
mName = name;
@@ -459,9 +474,9 @@
*
* @param width The buffer width in pixels.
* @param height The buffer height in pixels.
- * @hide
*/
- public Builder setBufferSize(int width, int height) {
+ public Builder setBufferSize(@IntRange(from = 0) int width,
+ @IntRange(from = 0) int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException(
"width and height must be positive");
@@ -474,8 +489,8 @@
/**
* Set the pixel format of the controlled surface's buffers, using constants from
* {@link android.graphics.PixelFormat}.
- * @hide
*/
+ @NonNull
public Builder setFormat(@PixelFormat.Format int format) {
mFormat = format;
return this;
@@ -490,6 +505,7 @@
* @param protectedContent Whether to require a protected sink.
* @hide
*/
+ @NonNull
public Builder setProtected(boolean protectedContent) {
if (protectedContent) {
mFlags |= PROTECTED_APP;
@@ -506,6 +522,7 @@
* not a complete prevention of readback as {@link #setProtected}.
* @hide
*/
+ @NonNull
public Builder setSecure(boolean secure) {
if (secure) {
mFlags |= SECURE;
@@ -537,8 +554,8 @@
* If the underlying buffer lacks an alpha channel, it is as if setOpaque(true)
* were set automatically.
* @param opaque Whether the Surface is OPAQUE.
- * @hide
*/
+ @NonNull
public Builder setOpaque(boolean opaque) {
if (opaque) {
mFlags |= OPAQUE;
@@ -556,9 +573,9 @@
* of the parent.
*
* @param parent The parent control.
- * @hide
*/
- public Builder setParent(SurfaceControl parent) {
+ @NonNull
+ public Builder setParent(@Nullable SurfaceControl parent) {
mParent = parent;
return this;
}
@@ -673,9 +690,6 @@
private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
SurfaceControl parent, int windowType, int ownerUid)
throws OutOfResourcesException, IllegalArgumentException {
- if (session == null) {
- throw new IllegalArgumentException("session must not be null");
- }
if (name == null) {
throw new IllegalArgumentException("name must not be null");
}
@@ -729,9 +743,6 @@
mCloseGuard.open("release");
}
- /**
- * @hide
- */
public void readFromParcel(Parcel in) {
if (in == null) {
throw new IllegalArgumentException("source must not be null");
@@ -748,17 +759,11 @@
assignNativeObject(object);
}
- /**
- * @hide
- */
@Override
public int describeContents() {
return 0;
}
- /**
- * @hide
- */
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
@@ -791,9 +796,6 @@
proto.end(token);
}
- /**
- * @hide
- */
public static final Creator<SurfaceControl> CREATOR
= new Creator<SurfaceControl>() {
public SurfaceControl createFromParcel(Parcel in) {
@@ -823,10 +825,12 @@
}
/**
- * Release the local reference to the server-side surface.
- * Always call release() when you're done with a Surface.
- * This will make the surface invalid.
- * @hide
+ * Release the local reference to the server-side surface. The surface
+ * may continue to exist on-screen as long as its parent continues
+ * to exist. To explicitly remove a surface from the screen use
+ * {@link Transaction#reparent} with a null-parent.
+ *
+ * Always call release() when you're done with a SurfaceControl.
*/
public void release() {
if (mNativeObject != 0) {
@@ -866,7 +870,10 @@
}
/**
- * @hide
+ * Check whether this instance points to a valid layer with the system-compositor. For
+ * example this may be false if construction failed, or the layer was released.
+ *
+ * @return Whether this SurfaceControl is valid.
*/
public boolean isValid() {
return mNativeObject != 0;
@@ -962,9 +969,9 @@
/**
* @hide
*/
- public void reparent(IBinder newParentHandle) {
+ public void reparent(SurfaceControl newParent) {
synchronized(SurfaceControl.class) {
- sGlobalTransaction.reparent(this, newParentHandle);
+ sGlobalTransaction.reparent(this, newParent);
}
}
@@ -1270,9 +1277,6 @@
}
}
- /**
- * @hide
- */
@Override
public String toString() {
return "Surface(name=" + mName + ")/@0x" +
@@ -1286,6 +1290,7 @@
/**
* Describes the properties of a physical display known to surface flinger.
+ * @hide
*/
public static final class PhysicalDisplayInfo {
/**
@@ -1777,9 +1782,12 @@
}
/**
- * @hide
+ * An atomic set of changes to a set of SurfaceControl.
*/
public static class Transaction implements Closeable {
+ /**
+ * @hide
+ */
public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
Transaction.class.getClassLoader(),
nativeGetNativeTransactionFinalizer(), 512);
@@ -1789,9 +1797,12 @@
Runnable mFreeNativeResources;
/**
- * @hide
+ * Open a new transaction object. The transaction may be filed with commands to
+ * manipulate {@link SurfaceControl} instances, and then applied atomically with
+ * {@link #apply}. Eventually the user should invoke {@link #close}, when the object
+ * is no longer required. Note however that re-using a transaction after a call to apply
+ * is allowed as a convenience.
*/
- @UnsupportedAppUsage
public Transaction() {
mNativeObject = nativeCreateTransaction();
mFreeNativeResources
@@ -1801,9 +1812,7 @@
/**
* Apply the transaction, clearing it's state, and making it usable
* as a new transaction.
- * @hide
*/
- @UnsupportedAppUsage
public void apply() {
apply(false);
}
@@ -1811,7 +1820,6 @@
/**
* Close the transaction, if the transaction was not already applied this will cancel the
* transaction.
- * @hide
*/
@Override
public void close() {
@@ -1841,6 +1849,27 @@
}
/**
+ * Toggle the visibility of a given Layer and it's sub-tree.
+ *
+ * @param sc The SurfaceControl for which to set the visibility
+ * @param visible The new visibility
+ * @return This transaction object.
+ */
+ @NonNull
+ public Transaction setVisibility(@NonNull SurfaceControl sc, boolean visible) {
+ sc.checkNotReleased();
+ if (visible) {
+ return show(sc);
+ } else {
+ return hide(sc);
+ }
+ }
+
+ /**
+ * Request that a given surface and it's sub-tree be shown.
+ *
+ * @param sc The surface to show.
+ * @return This transaction.
* @hide
*/
@UnsupportedAppUsage
@@ -1851,6 +1880,10 @@
}
/**
+ * Request that a given surface and it's sub-tree be hidden.
+ *
+ * @param sc The surface to hidden.
+ * @return This transaction.
* @hide
*/
@UnsupportedAppUsage
@@ -1871,10 +1904,17 @@
}
/**
- * @hide
+ * Set the default buffer size for the SurfaceControl, if there is an
+ * {@link Surface} assosciated with the control, then
+ * this will be the default size for buffers dequeued from it.
+ * @param sc The surface to set the buffer size for.
+ * @param w The default width
+ * @param h The default height
+ * @return This Transaction
*/
- @UnsupportedAppUsage
- public Transaction setBufferSize(SurfaceControl sc, int w, int h) {
+ @NonNull
+ public Transaction setBufferSize(@NonNull SurfaceControl sc,
+ @IntRange(from = 0) int w, @IntRange(from = 0) int h) {
sc.checkNotReleased();
mResizedSurfaces.put(sc, new Point(w, h));
nativeSetSize(mNativeObject, sc.mNativeObject, w, h);
@@ -1882,10 +1922,17 @@
}
/**
- * @hide
+ * Set the Z-order for a given SurfaceControl, relative to it's siblings.
+ * If two siblings share the same Z order the ordering is undefined. Surfaces
+ * with a negative Z will be placed below the parent surface.
+ *
+ * @param sc The SurfaceControl to set the Z order on
+ * @param z The Z-order
+ * @return This Transaction.
*/
- @UnsupportedAppUsage
- public Transaction setLayer(SurfaceControl sc, int z) {
+ @NonNull
+ public Transaction setLayer(@NonNull SurfaceControl sc,
+ @IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int z) {
sc.checkNotReleased();
nativeSetLayer(mNativeObject, sc.mNativeObject, z);
return this;
@@ -1912,10 +1959,15 @@
}
/**
- * @hide
+ * Set the alpha for a given surface. If the alpha is non-zero the SurfaceControl
+ * will be blended with the Surfaces under it according to the specified ratio.
+ *
+ * @param sc The given SurfaceControl.
+ * @param alpha The alpha to set.
*/
- @UnsupportedAppUsage
- public Transaction setAlpha(SurfaceControl sc, float alpha) {
+ @NonNull
+ public Transaction setAlpha(@NonNull SurfaceControl sc,
+ @FloatRange(from = 0.0, to = 1.0) float alpha) {
sc.checkNotReleased();
nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
return this;
@@ -1947,6 +1999,25 @@
}
/**
+ * Specify how the buffer assosciated with this Surface is mapped in to the
+ * parent coordinate space. The source frame will be scaled to fit the destination
+ * frame, after being rotated according to the orientation parameter.
+ *
+ * @param sc The SurfaceControl to specify the geometry of
+ * @param sourceCrop The source rectangle in buffer space. Or null for the entire buffer.
+ * @param destFrame The destination rectangle in parent space. Or null for the source frame.
+ * @param orientation The buffer rotation
+ * @return This transaction object.
+ */
+ @NonNull
+ public Transaction setGeometry(@NonNull SurfaceControl sc, @Nullable Rect sourceCrop,
+ @Nullable Rect destFrame, @Surface.Rotation int orientation) {
+ sc.checkNotReleased();
+ nativeSetGeometry(mNativeObject, sc.mNativeObject, sourceCrop, destFrame, orientation);
+ return this;
+ }
+
+ /**
* @hide
*/
@UnsupportedAppUsage
@@ -2023,20 +2094,20 @@
return this;
}
- @UnsupportedAppUsage
/**
* @hide
*/
+ @UnsupportedAppUsage
public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
sc.checkNotReleased();
nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack);
return this;
}
- @UnsupportedAppUsage
/**
* @hide
*/
+ @UnsupportedAppUsage
public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle,
long frameNumber) {
if (frameNumber < 0) {
@@ -2047,10 +2118,10 @@
return this;
}
- @UnsupportedAppUsage
/**
* @hide
*/
+ @UnsupportedAppUsage
public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface,
long frameNumber) {
if (frameNumber < 0) {
@@ -2071,13 +2142,25 @@
return this;
}
- /** Re-parents a specific child layer to a new parent
- * @hide
+ /**
+ * Re-parents a given layer to a new parent. Children inherit transform (position, scaling)
+ * crop, visibility, and Z-ordering from their parents, as if the children were pixels within the
+ * parent Surface.
+ *
+ * @param sc The SurfaceControl to reparent
+ * @param newParent The new parent for the given control.
+ * @return This Transaction
*/
- public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) {
+ @NonNull
+ public Transaction reparent(@NonNull SurfaceControl sc,
+ @Nullable SurfaceControl newParent) {
sc.checkNotReleased();
- nativeReparent(mNativeObject, sc.mNativeObject,
- newParentHandle);
+ long otherObject = 0;
+ if (newParent != null) {
+ newParent.checkNotReleased();
+ otherObject = newParent.mNativeObject;
+ }
+ nativeReparent(mNativeObject, sc.mNativeObject, otherObject);
return this;
}
@@ -2245,9 +2328,12 @@
/**
* Merge the other transaction into this transaction, clearing the
* other transaction as if it had been applied.
- * @hide
+ *
+ * @param other The transaction to merge in to this one.
+ * @return This transaction.
*/
- public Transaction merge(Transaction other) {
+ @NonNull
+ public Transaction merge(@NonNull Transaction other) {
mResizedSurfaces.putAll(other.mResizedSurfaces);
other.mResizedSurfaces.clear();
nativeMergeTransaction(mNativeObject, other.mNativeObject);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 61fb00d..45e6c50 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -120,10 +120,11 @@
final Rect mScreenRect = new Rect();
SurfaceSession mSurfaceSession;
- SurfaceControlWithBackground mSurfaceControl;
+ SurfaceControl mSurfaceControl;
// In the case of format changes we switch out the surface in-place
// we need to preserve the old one until the new one has drawn.
SurfaceControl mDeferredDestroySurfaceControl;
+ SurfaceControl mBackgroundControl;
final Rect mTmpRect = new Rect();
final Configuration mConfiguration = new Configuration();
@@ -487,6 +488,29 @@
}
}
+ private void updateBackgroundVisibilityInTransaction() {
+ if (mBackgroundControl == null) {
+ return;
+ }
+ if ((mSurfaceFlags & PixelFormat.OPAQUE) == 0) {
+ mBackgroundControl.show();
+ mBackgroundControl.setLayer(Integer.MIN_VALUE);
+ } else {
+ mBackgroundControl.hide();
+ }
+ }
+
+ private void releaseSurfaces() {
+ if (mSurfaceControl != null) {
+ mSurfaceControl.destroy();
+ mSurfaceControl = null;
+ }
+ if (mBackgroundControl != null) {
+ mBackgroundControl.destroy();
+ mBackgroundControl = null;
+ }
+ }
+
/** @hide */
protected void updateSurface() {
if (!mHaveFrame) {
@@ -553,14 +577,21 @@
updateOpaqueFlag();
final String name = "SurfaceView - " + viewRoot.getTitle().toString();
- mSurfaceControl = new SurfaceControlWithBackground(
- name,
- (mSurfaceFlags & SurfaceControl.OPAQUE) != 0,
- new SurfaceControl.Builder(mSurfaceSession)
- .setBufferSize(mSurfaceWidth, mSurfaceHeight)
- .setFormat(mFormat)
- .setParent(viewRoot.getSurfaceControl())
- .setFlags(mSurfaceFlags));
+ mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+ .setName(name)
+ .setOpaque((mSurfaceFlags & SurfaceControl.OPAQUE) != 0)
+ .setBufferSize(mSurfaceWidth, mSurfaceHeight)
+ .setFormat(mFormat)
+ .setParent(viewRoot.getSurfaceControl())
+ .setFlags(mSurfaceFlags)
+ .build();
+ mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession)
+ .setName("Background for -" + name)
+ .setOpaque(true)
+ .setColorLayer(true)
+ .setParent(mSurfaceControl)
+ .build();
+
} else if (mSurfaceControl == null) {
return;
}
@@ -577,11 +608,13 @@
SurfaceControl.openTransaction();
try {
mSurfaceControl.setLayer(mSubLayer);
+
if (mViewVisibility) {
mSurfaceControl.show();
} else {
mSurfaceControl.hide();
}
+ updateBackgroundVisibilityInTransaction();
// While creating the surface, we will set it's initial
// geometry. Outside of that though, we should generally
@@ -724,8 +757,7 @@
if (mSurfaceControl != null && !mSurfaceCreated) {
mSurface.release();
- mSurfaceControl.destroy();
- mSurfaceControl = null;
+ releaseSurfaces();
}
}
} catch (Exception ex) {
@@ -823,7 +855,6 @@
final ViewRootImpl viewRoot = getViewRootImpl();
applySurfaceTransforms(mSurfaceControl, position, frameNumber);
- applySurfaceTransforms(mSurfaceControl.mBackgroundControl, position, frameNumber);
applyChildSurfaceTransaction_renderWorker(mRtTransaction, viewRoot.mSurface,
frameNumber);
@@ -950,7 +981,19 @@
* @hide
*/
public void setResizeBackgroundColor(int bgColor) {
- mSurfaceControl.setBackgroundColor(bgColor);
+ if (mBackgroundControl == null) {
+ return;
+ }
+
+ final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f,
+ Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f };
+
+ SurfaceControl.openTransaction();
+ try {
+ mBackgroundControl.setColor(colorComponents);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
}
@UnsupportedAppUsage
@@ -1128,154 +1171,12 @@
};
/**
- * @hide
+ * Return a SurfaceControl which can be used for parenting Surfaces to
+ * this SurfaceView.
+ *
+ * @return The SurfaceControl for this SurfaceView.
*/
public SurfaceControl getSurfaceControl() {
return mSurfaceControl;
}
-
- class SurfaceControlWithBackground extends SurfaceControl {
- SurfaceControl mBackgroundControl;
- private boolean mOpaque = true;
- public boolean mVisible = false;
-
- public SurfaceControlWithBackground(String name, boolean opaque, SurfaceControl.Builder b)
- throws Exception {
- super(b.setName(name).build());
-
- mBackgroundControl = b.setName("Background for -" + name)
- .setFormat(OPAQUE)
- // Unset the buffer size of the background color layer.
- .setBufferSize(0, 0)
- .setColorLayer(true)
- .build();
- mOpaque = opaque;
- }
-
- @Override
- public void setAlpha(float alpha) {
- super.setAlpha(alpha);
- mBackgroundControl.setAlpha(alpha);
- }
-
- @Override
- public void setLayer(int zorder) {
- super.setLayer(zorder);
- // -3 is below all other child layers as SurfaceView never goes below -2
- mBackgroundControl.setLayer(-3);
- }
-
- @Override
- public void setPosition(float x, float y) {
- super.setPosition(x, y);
- mBackgroundControl.setPosition(x, y);
- }
-
- @Override
- public void setBufferSize(int w, int h) {
- super.setBufferSize(w, h);
- // The background surface is a color layer so we do not set a size.
- }
-
- @Override
- public void setWindowCrop(Rect crop) {
- super.setWindowCrop(crop);
- mBackgroundControl.setWindowCrop(crop);
- }
-
- @Override
- public void setWindowCrop(int width, int height) {
- super.setWindowCrop(width, height);
- mBackgroundControl.setWindowCrop(width, height);
- }
-
- @Override
- public void setLayerStack(int layerStack) {
- super.setLayerStack(layerStack);
- mBackgroundControl.setLayerStack(layerStack);
- }
-
- @Override
- public void setOpaque(boolean isOpaque) {
- super.setOpaque(isOpaque);
- mOpaque = isOpaque;
- updateBackgroundVisibility();
- }
-
- @Override
- public void setSecure(boolean isSecure) {
- super.setSecure(isSecure);
- }
-
- @Override
- public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
- super.setMatrix(dsdx, dtdx, dsdy, dtdy);
- mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy);
- }
-
- @Override
- public void hide() {
- super.hide();
- mVisible = false;
- updateBackgroundVisibility();
- }
-
- @Override
- public void show() {
- super.show();
- mVisible = true;
- updateBackgroundVisibility();
- }
-
- @Override
- public void destroy() {
- super.destroy();
- mBackgroundControl.destroy();
- }
-
- @Override
- public void release() {
- super.release();
- mBackgroundControl.release();
- }
-
- @Override
- public void setTransparentRegionHint(Region region) {
- super.setTransparentRegionHint(region);
- mBackgroundControl.setTransparentRegionHint(region);
- }
-
- @Override
- public void deferTransactionUntil(IBinder handle, long frame) {
- super.deferTransactionUntil(handle, frame);
- mBackgroundControl.deferTransactionUntil(handle, frame);
- }
-
- @Override
- public void deferTransactionUntil(Surface barrier, long frame) {
- super.deferTransactionUntil(barrier, frame);
- mBackgroundControl.deferTransactionUntil(barrier, frame);
- }
-
- /** Set the color to fill the background with. */
- private void setBackgroundColor(int bgColor) {
- final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f,
- Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f };
-
- SurfaceControl.openTransaction();
- try {
- mBackgroundControl.setColor(colorComponents);
- } finally {
- SurfaceControl.closeTransaction();
- }
- }
-
- void updateBackgroundVisibility() {
- if (mOpaque && mVisible) {
- mBackgroundControl.show();
- } else {
- mBackgroundControl.hide();
- }
- }
- }
}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 34d076f..47b206ca 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.HardwareRenderer;
+import android.graphics.Picture;
import android.graphics.Point;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
@@ -553,6 +554,10 @@
dumpProfileInfo(fd, flags);
}
+ Picture captureRenderingCommands() {
+ return null;
+ }
+
@Override
public boolean loadSystemProperties() {
boolean changed = super.loadSystemProperties();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a1aa06e..483280e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
import static java.lang.Math.max;
@@ -5123,7 +5124,7 @@
sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P;
- sBrokenInsetsDispatch = !ViewRootImpl.USE_NEW_INSETS
+ sBrokenInsetsDispatch = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
|| targetSdkVersion < Build.VERSION_CODES.Q;
sCompatibilityDone = true;
@@ -8202,10 +8203,10 @@
* {@link ContentCaptureSession#notifyViewDisappeared(AutofillId)}, and
* {@link ContentCaptureSession#notifyViewTextChanged(AutofillId, CharSequence, int)}
* respectively. The structure for the a child must be created using
- * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, int)}, and the
+ * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, long)}, and the
* {@code autofillId} for a child can be obtained either through
* {@code childStructure.getAutofillId()} or
- * {@link ContentCaptureSession#newAutofillId(AutofillId, int)}.
+ * {@link ContentCaptureSession#newAutofillId(AutofillId, long)}.
*
* <p><b>Note: </b>the following methods of the {@code structure} will be ignored:
* <ul>
@@ -8226,7 +8227,15 @@
* </ul>
*/
public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
- onProvideStructure(structure, VIEW_STRUCTURE_FOR_CONTENT_CAPTURE, flags);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "onProvideContentCaptureStructure() for " + getClass().getSimpleName());
+ }
+ try {
+ onProvideStructure(structure, VIEW_STRUCTURE_FOR_CONTENT_CAPTURE, flags);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
}
/** @hide */
@@ -8599,7 +8608,7 @@
if (isAttachedToWindow()) {
throw new IllegalStateException("Cannot set autofill id when view is attached");
}
- if (id != null && id.isVirtual()) {
+ if (id != null && !id.isNonVirtual()) {
throw new IllegalStateException("Cannot set autofill id assigned to virtual views");
}
if (id == null && (mPrivateFlags3 & PFLAG3_AUTOFILLID_EXPLICITLY_SET) == 0) {
@@ -9016,6 +9025,18 @@
* </ol>
*/
private void notifyAppearedOrDisappearedForContentCaptureIfNeeded(boolean appeared) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "notifyContentCapture(" + appeared + ") for " + getClass().getSimpleName());
+ }
+ try {
+ notifyAppearedOrDisappearedForContentCaptureIfNeededNoTrace(appeared);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+
+ private void notifyAppearedOrDisappearedForContentCaptureIfNeededNoTrace(boolean appeared) {
// First check if context has client, so it saves a service lookup when it doesn't
if (!mContext.isContentCaptureSupported()) return;
@@ -13973,7 +13994,7 @@
if (clickable) {
setPressed(true, x, y);
}
- checkForLongClick(0, x, y);
+ checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
return true;
}
}
@@ -14714,7 +14735,7 @@
mHasPerformedLongPress = false;
if (!clickable) {
- checkForLongClick(0, x, y);
+ checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
break;
}
@@ -14738,7 +14759,7 @@
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
- checkForLongClick(0, x, y);
+ checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
}
break;
@@ -25413,7 +25434,7 @@
}
}
- private void checkForLongClick(int delayOffset, float x, float y) {
+ private void checkForLongClick(long delay, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
@@ -25423,8 +25444,7 @@
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
- postDelayed(mPendingCheckForLongPress,
- ViewConfiguration.getLongPressTimeout() - delayOffset);
+ postDelayed(mPendingCheckForLongPress, delay);
}
}
@@ -27014,7 +27034,9 @@
public void run() {
mPrivateFlags &= ~PFLAG_PREPRESSED;
setPressed(true, x, y);
- checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
+ final long delay =
+ ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout();
+ checkForLongClick(delay, x, y);
}
}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 292e933..5afc07f 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -17,17 +17,21 @@
package android.view;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.HardwareRenderer;
import android.graphics.Picture;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RenderNode;
import android.os.Debug;
import android.os.Handler;
+import android.os.Looper;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -48,16 +52,20 @@
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
/**
* Various debugging/tracing tools related to {@link View} and the view hierarchy.
@@ -741,6 +749,123 @@
root.getViewRootImpl().outputDisplayList(target);
}
+ private static class PictureCallbackHandler implements AutoCloseable,
+ HardwareRenderer.PictureCapturedCallback, Runnable {
+ private final HardwareRenderer mRenderer;
+ private final Function<Picture, Boolean> mCallback;
+ private final Executor mExecutor;
+ private final ReentrantLock mLock = new ReentrantLock(false);
+ private final ArrayDeque<Picture> mQueue = new ArrayDeque<>(3);
+ private boolean mStopListening;
+ private Thread mRenderThread;
+
+ private PictureCallbackHandler(HardwareRenderer renderer,
+ Function<Picture, Boolean> callback, Executor executor) {
+ mRenderer = renderer;
+ mCallback = callback;
+ mExecutor = executor;
+ mRenderer.setPictureCaptureCallback(this);
+ }
+
+ @Override
+ public void close() {
+ mLock.lock();
+ mStopListening = true;
+ mLock.unlock();
+ mRenderer.setPictureCaptureCallback(null);
+ }
+
+ @Override
+ public void onPictureCaptured(Picture picture) {
+ mLock.lock();
+ if (mStopListening) {
+ mLock.unlock();
+ mRenderer.setPictureCaptureCallback(null);
+ return;
+ }
+ if (mRenderThread == null) {
+ mRenderThread = Thread.currentThread();
+ }
+ Picture toDestroy = null;
+ if (mQueue.size() == 3) {
+ toDestroy = mQueue.removeLast();
+ }
+ mQueue.add(picture);
+ mLock.unlock();
+ if (toDestroy == null) {
+ mExecutor.execute(this);
+ } else {
+ toDestroy.close();
+ }
+ }
+
+ @Override
+ public void run() {
+ mLock.lock();
+ final Picture picture = mQueue.poll();
+ final boolean isStopped = mStopListening;
+ mLock.unlock();
+ if (Thread.currentThread() == mRenderThread) {
+ close();
+ throw new IllegalStateException(
+ "ViewDebug#startRenderingCommandsCapture must be given an executor that "
+ + "invokes asynchronously");
+ }
+ if (isStopped) {
+ picture.close();
+ return;
+ }
+ final boolean keepReceiving = mCallback.apply(picture);
+ if (!keepReceiving) {
+ close();
+ }
+ }
+ }
+
+ /**
+ * Begins capturing the entire rendering commands for the view tree referenced by the given
+ * view. The view passed may be any View in the tree as long as it is attached. That is,
+ * {@link View#isAttachedToWindow()} must be true.
+ *
+ * Every time a frame is rendered a Picture will be passed to the given callback via the given
+ * executor. As long as the callback returns 'true' it will continue to receive new frames.
+ * The system will only invoke the callback at a rate that the callback is able to keep up with.
+ * That is, if it takes 48ms for the callback to complete and there is a 60fps animation running
+ * then the callback will only receive 33% of the frames produced.
+ *
+ * This method must be called on the same thread as the View tree.
+ *
+ * @param tree The View tree to capture the rendering commands.
+ * @param callback The callback to invoke on every frame produced. Should return true to
+ * continue receiving new frames, false to stop capturing.
+ * @param executor The executor to invoke the callback on. Recommend using a background thread
+ * to avoid stalling the UI thread. Must be an asynchronous invoke or an
+ * exception will be thrown.
+ * @return a closeable that can be used to stop capturing. May be invoked on any thread. Note
+ * that the callback may continue to receive another frame or two depending on thread timings.
+ * Returns null if the capture stream cannot be started, such as if there's no
+ * HardwareRenderer for the given view tree.
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor,
+ Function<Picture, Boolean> callback) {
+ final View.AttachInfo attachInfo = tree.mAttachInfo;
+ if (attachInfo == null) {
+ throw new IllegalArgumentException("Given view isn't attached");
+ }
+ if (attachInfo.mHandler.getLooper() != Looper.myLooper()) {
+ throw new IllegalStateException("Called on the wrong thread."
+ + " Must be called on the thread that owns the given View");
+ }
+ final HardwareRenderer renderer = attachInfo.mThreadedRenderer;
+ if (renderer != null) {
+ return new PictureCallbackHandler(renderer, callback, executor);
+ }
+ return null;
+ }
+
private static void capture(View root, final OutputStream clientStream, String parameter)
throws IOException {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 27d4ea4..a031b70 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -163,13 +163,16 @@
private static final boolean MT_RENDERER_AVAILABLE = true;
/**
- * If set to true, the view system will switch from using rectangles retrieved from window to
+ * If set to 2, the view system will switch from using rectangles retrieved from window to
* dispatch to the view hierarchy to using {@link InsetsController}, that derives the insets
* directly from the full configuration, enabling richer information about the insets state, as
* well as new APIs to control it frame-by-frame, and synchronize animations with it.
* <p>
- * Only switch this to true once the new insets system is productionized and the old APIs are
+ * Only set this to 2 once the new insets system is productionized and the old APIs are
* fully migrated over.
+ * <p>
+ * If set to 1, this will switch to a mode where we only use the new approach for IME, but not
+ * for the status/navigation bar.
*/
private static final String USE_NEW_INSETS_PROPERTY = "persist.wm.new_insets";
@@ -177,8 +180,26 @@
* @see #USE_NEW_INSETS_PROPERTY
* @hide
*/
- public static final boolean USE_NEW_INSETS =
- SystemProperties.getBoolean(USE_NEW_INSETS_PROPERTY, false);
+ public static final int sNewInsetsMode =
+ SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, 0);
+
+ /**
+ * @see #USE_NEW_INSETS_PROPERTY
+ * @hide
+ */
+ public static final int NEW_INSETS_MODE_NONE = 0;
+
+ /**
+ * @see #USE_NEW_INSETS_PROPERTY
+ * @hide
+ */
+ public static final int NEW_INSETS_MODE_IME = 1;
+
+ /**
+ * @see #USE_NEW_INSETS_PROPERTY
+ * @hide
+ */
+ public static final int NEW_INSETS_MODE_FULL = 2;
/**
* Set this system property to true to force the view hierarchy to render
@@ -1367,7 +1388,7 @@
}
void notifyInsetsChanged() {
- if (!USE_NEW_INSETS) {
+ if (sNewInsetsMode == NEW_INSETS_MODE_NONE) {
return;
}
mApplyInsetsRequested = true;
@@ -1855,10 +1876,11 @@
}
contentInsets = ensureInsetsNonNegative(contentInsets, "content");
stableInsets = ensureInsetsNonNegative(stableInsets, "stable");
- if (USE_NEW_INSETS) {
+ if (sNewInsetsMode != NEW_INSETS_MODE_NONE) {
mLastWindowInsets = mInsetsController.calculateInsets(
mContext.getResources().getConfiguration().isScreenRound(),
- mAttachInfo.mAlwaysConsumeNavBar, displayCutout);
+ mAttachInfo.mAlwaysConsumeNavBar, displayCutout,
+ contentInsets, stableInsets);
} else {
mLastWindowInsets = new WindowInsets(contentInsets, stableInsets,
mContext.getResources().getConfiguration().isScreenRound(),
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 9ac0ca9..e808830 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -217,7 +217,10 @@
return typeInsetMap;
}
- private static void assignCompatInsets(Insets[] typeInsetMap, Rect insets) {
+ /**
+ * @hide
+ */
+ static void assignCompatInsets(Insets[] typeInsetMap, Rect insets) {
typeInsetMap[indexOf(TOP_BAR)] = Insets.of(0, insets.top, 0, 0);
typeInsetMap[indexOf(SIDE_BARS)] = Insets.of(insets.left, 0, insets.right, insets.bottom);
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 45c3651..6326c59 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -255,6 +255,12 @@
int TRANSIT_CRASHING_ACTIVITY_CLOSE = 26;
/**
+ * A task is changing windowing modes
+ * @hide
+ */
+ int TRANSIT_TASK_CHANGE_WINDOWING_MODE = 27;
+
+ /**
* @hide
*/
@IntDef(prefix = { "TRANSIT_" }, value = {
@@ -280,7 +286,8 @@
TRANSIT_KEYGUARD_UNOCCLUDE,
TRANSIT_TRANSLUCENT_ACTIVITY_OPEN,
TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE,
- TRANSIT_CRASHING_ACTIVITY_CLOSE
+ TRANSIT_CRASHING_ACTIVITY_CLOSE,
+ TRANSIT_TASK_CHANGE_WINDOWING_MODE
})
@Retention(RetentionPolicy.SOURCE)
@interface TransitionType {}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index c5c1bca..6aafa34 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1166,9 +1166,10 @@
/**
* Notifies that the accessibility button in the system's navigation area has been clicked
*
+ * @param displayId The logical display id.
* @hide
*/
- public void notifyAccessibilityButtonClicked() {
+ public void notifyAccessibilityButtonClicked(int displayId) {
final IAccessibilityManager service;
synchronized (mLock) {
service = getServiceLocked();
@@ -1177,7 +1178,7 @@
}
}
try {
- service.notifyAccessibilityButtonClicked();
+ service.notifyAccessibilityButtonClicked(displayId);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 38dac94..486b35d 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -63,7 +63,7 @@
IBinder getWindowToken(int windowId, int userId);
- void notifyAccessibilityButtonClicked();
+ void notifyAccessibilityButtonClicked(int displayId);
void notifyAccessibilityButtonVisibilityChanged(boolean available);
diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java
index 9c935af..f1c7b69 100644
--- a/core/java/android/view/autofill/AutofillId.java
+++ b/core/java/android/view/autofill/AutofillId.java
@@ -29,12 +29,14 @@
/** @hide */
public static final int NO_SESSION = 0;
- private static final int FLAG_IS_VIRTUAL = 0x1;
- private static final int FLAG_HAS_SESSION = 0x2;
+ private static final int FLAG_IS_VIRTUAL_INT = 0x1;
+ private static final int FLAG_IS_VIRTUAL_LONG = 0x2;
+ private static final int FLAG_HAS_SESSION = 0x4;
private final int mViewId;
private final int mFlags;
- private final int mVirtualId;
+ private final int mVirtualIntId;
+ private final long mVirtualLongId;
private final int mSessionId;
/** @hide */
@@ -46,40 +48,89 @@
/** @hide */
@TestApi
public AutofillId(@NonNull AutofillId parent, int virtualChildId) {
- this(FLAG_IS_VIRTUAL, parent.mViewId, virtualChildId, NO_SESSION);
+ this(FLAG_IS_VIRTUAL_INT, parent.mViewId, virtualChildId, NO_SESSION);
}
/** @hide */
public AutofillId(int parentId, int virtualChildId) {
- this(FLAG_IS_VIRTUAL, parentId, virtualChildId, NO_SESSION);
+ this(FLAG_IS_VIRTUAL_INT, parentId, virtualChildId, NO_SESSION);
}
/** @hide */
- public AutofillId(@NonNull AutofillId parent, int virtualChildId, int sessionId) {
- this(FLAG_IS_VIRTUAL | FLAG_HAS_SESSION, parent.mViewId, virtualChildId, sessionId);
+ public AutofillId(@NonNull AutofillId parent, long virtualChildId, int sessionId) {
+ this(FLAG_IS_VIRTUAL_LONG | FLAG_HAS_SESSION, parent.mViewId, virtualChildId, sessionId);
}
- private AutofillId(int flags, int parentId, int virtualChildId, int sessionId) {
+ private AutofillId(int flags, int parentId, long virtualChildId, int sessionId) {
mFlags = flags;
mViewId = parentId;
- mVirtualId = virtualChildId;
+ mVirtualIntId = ((flags & FLAG_IS_VIRTUAL_INT) != 0) ? (int) virtualChildId : View.NO_ID;
+ mVirtualLongId = ((flags & FLAG_IS_VIRTUAL_LONG) != 0) ? virtualChildId : View.NO_ID;
mSessionId = sessionId;
}
-
/** @hide */
public int getViewId() {
return mViewId;
}
- /** @hide */
- public int getVirtualChildId() {
- return mVirtualId;
+ /**
+ * Gets the virtual child id.
+ *
+ * <p>Should only be used on subsystems where such id is represented by an {@code int}
+ * (Assist and Autofill).
+ *
+ * @hide
+ */
+ public int getVirtualChildIntId() {
+ return mVirtualIntId;
}
- /** @hide */
- public boolean isVirtual() {
- return (mFlags & FLAG_IS_VIRTUAL) != 0;
+ /**
+ * Gets the virtual child id.
+ *
+ * <p>Should only be used on subsystems where such id is represented by a {@code long}
+ * (ContentCapture).
+ *
+ * @hide
+ */
+ public long getVirtualChildLongId() {
+ return mVirtualLongId;
+ }
+
+ /**
+ * Checks whether this node represents a virtual child, whose id is represented by an
+ * {@code int}.
+ *
+ * <p>Should only be used on subsystems where such id is represented by an {@code int}
+ * (Assist and Autofill).
+ *
+ * @hide
+ */
+ public boolean isVirtualInt() {
+ return (mFlags & FLAG_IS_VIRTUAL_INT) != 0;
+ }
+
+ /**
+ * Checks whether this node represents a virtual child, whose id is represented by an
+ * {@code long}.
+ *
+ * <p>Should only be used on subsystems where such id is represented by a {@code long}
+ * (ContentCapture).
+ *
+ * @hide
+ */
+ public boolean isVirtualLong() {
+ return (mFlags & FLAG_IS_VIRTUAL_LONG) != 0;
+ }
+
+ /**
+ * Checks whether this node represents a non-virtual child.
+ *
+ * @hide
+ */
+ public boolean isNonVirtual() {
+ return !isVirtualInt() && !isVirtualLong();
}
private boolean hasSession() {
@@ -100,7 +151,8 @@
final int prime = 31;
int result = 1;
result = prime * result + mViewId;
- result = prime * result + mVirtualId;
+ result = prime * result + mVirtualIntId;
+ result = prime * result + (int) (mVirtualLongId ^ (mVirtualLongId >>> 32));
result = prime * result + mSessionId;
return result;
}
@@ -112,7 +164,8 @@
if (getClass() != obj.getClass()) return false;
final AutofillId other = (AutofillId) obj;
if (mViewId != other.mViewId) return false;
- if (mVirtualId != other.mVirtualId) return false;
+ if (mVirtualIntId != other.mVirtualIntId) return false;
+ if (mVirtualLongId != other.mVirtualLongId) return false;
if (mSessionId != other.mSessionId) return false;
return true;
}
@@ -120,9 +173,12 @@
@Override
public String toString() {
final StringBuilder builder = new StringBuilder().append(mViewId);
- if (isVirtual()) {
- builder.append(':').append(mVirtualId);
+ if (isVirtualInt()) {
+ builder.append(':').append(mVirtualIntId);
+ } else if (isVirtualLong()) {
+ builder.append(':').append(mVirtualLongId);
}
+
if (hasSession()) {
builder.append('@').append(mSessionId);
}
@@ -138,12 +194,14 @@
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mViewId);
parcel.writeInt(mFlags);
- if (isVirtual()) {
- parcel.writeInt(mVirtualId);
- }
if (hasSession()) {
parcel.writeInt(mSessionId);
}
+ if (isVirtualInt()) {
+ parcel.writeInt(mVirtualIntId);
+ } else if (isVirtualLong()) {
+ parcel.writeLong(mVirtualLongId);
+ }
}
public static final Parcelable.Creator<AutofillId> CREATOR =
@@ -152,9 +210,14 @@
public AutofillId createFromParcel(Parcel source) {
final int viewId = source.readInt();
final int flags = source.readInt();
- final int virtualId = (flags & FLAG_IS_VIRTUAL) != 0 ? source.readInt() : View.NO_ID;
final int sessionId = (flags & FLAG_HAS_SESSION) != 0 ? source.readInt() : NO_SESSION;
- return new AutofillId(flags, viewId, virtualId, sessionId);
+ if ((flags & FLAG_IS_VIRTUAL_INT) != 0) {
+ return new AutofillId(flags, viewId, source.readInt(), sessionId);
+ }
+ if ((flags & FLAG_IS_VIRTUAL_LONG) != 0) {
+ return new AutofillId(flags, viewId, source.readLong(), sessionId);
+ }
+ return new AutofillId(flags, viewId, View.NO_ID, sessionId);
}
@Override
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 888a4c5..64c34f61 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -17,6 +17,7 @@
package android.view.autofill;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.util.DebugUtils.flagsToString;
import static android.view.autofill.Helper.sDebug;
import static android.view.autofill.Helper.sVerbose;
@@ -25,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.ComponentName;
@@ -77,6 +79,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
//TODO: use java.lang.ref.Cleaner once Android supports Java 9
import sun.misc.Cleaner;
@@ -336,6 +339,25 @@
public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
/**
+ * Displays the Augment Autofill window using the same mechanism (such as a popup-window
+ * attached to the focused view) as the standard autofill.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int FLAG_SMART_SUGGESTION_SYSTEM = 0x1;
+
+ /** @hide */ // TODO(b/123233342): remove when not used anymore
+ public static final int FLAG_SMART_SUGGESTION_LEGACY = 0x2;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_SMART_SUGGESTION_" }, value = {
+ FLAG_SMART_SUGGESTION_SYSTEM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SmartSuggestionMode {}
+
+ /**
* Makes an authentication id from a request id and a dataset id.
*
* @param requestId The request id.
@@ -1686,7 +1708,7 @@
final IAutoFillManager service = mService;
final IAutoFillManagerClient serviceClient = mServiceClient;
mServiceClientCleaner = Cleaner.create(this, () -> {
- // TODO(b/111330312): call service to also remove reference to
+ // TODO(b/123100811): call service to also remove reference to
// mAugmentedAutofillServiceClient
try {
service.removeClient(serviceClient, userId);
@@ -1746,6 +1768,108 @@
}
}
+ /**
+ * Defines whether augmented autofill should be triggered for activities with such
+ * {@link android.content.ComponentName}.
+ *
+ * <p>Useful to blacklist a particular activity.
+ *
+ * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+ * service, and it's ignored if the caller isn't it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+ //in the same package as the test, and that module is compiled with SDK=test_current
+ public void setActivityAugmentedAutofillEnabled(@NonNull ComponentName activity,
+ boolean enabled) {
+ // TODO(b/123100824): implement
+ }
+
+ /**
+ * Defines whether augmented autofill should be triggered for activities of the app with such
+ * {@code packageName}.
+ *
+ * <p>Useful to blacklist any activity from a particular app.
+ *
+ * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+ * service, and it's ignored if the caller isn't it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+ //in the same package as the test, and that module is compiled with SDK=test_current
+ public void setPackageAugmentedAutofillEnabled(@NonNull String packageName, boolean enabled) {
+ // TODO(b/123100824): implement
+ }
+
+ /**
+ * Explicitly limits augmented autofill to the given packages and activities.
+ *
+ * <p>When the whitelist is set, it overrides the values passed to
+ * {@link #setActivityAugmentedAutofillEnabled(ComponentName, boolean)}
+ * and {@link #setPackageAugmentedAutofillEnabled(String, boolean)}.
+ *
+ * <p>To reset the whitelist, call it passing {@code null} to both arguments.
+ *
+ * <p>Useful when the service wants to restrict augmented autofill to a category of apps, like
+ * apps that uses addresses. For example, if the service wants to support augmented autofill on
+ * all activities of app {@code AddressApp1} and just activities {@code act1} and {@code act2}
+ * of {@code AddressApp2}, it would call:
+ * {@code setAugmentedAutofillWhitelist(Arrays.asList("AddressApp1"),
+ * Arrays.asList(new ComponentName("AddressApp2", "act1"),
+ * new ComponentName("AddressApp2", "act2")));}
+ *
+ * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+ * service, and it's ignored if the caller isn't it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+ //in the same package as the test, and that module is compiled with SDK=test_current
+ public void setAugmentedAutofillWhitelist(@Nullable List<String> packages,
+ @Nullable List<ComponentName> activities) {
+ // TODO(b/123100824): implement
+ }
+
+ /**
+ * Gets the activities where augmented autofill was disabled by
+ * {@link #setActivityAugmentedAutofillEnabled(ComponentName, boolean)}.
+ *
+ * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+ * service, and it's ignored if the caller isn't it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @NonNull
+ public Set<ComponentName> getAugmentedAutofillDisabledActivities() {
+ return null; // TODO(b/123100824): implement
+ }
+
+ /**
+ * Gets the apps where content capture was disabled by
+ * {@link #setPackageAugmentedAutofillEnabled(String, boolean)}.
+ *
+ * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+ * service, and it's ignored if the caller isn't it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @NonNull
+ public Set<String> getAugmentedAutofillDisabledPackages() {
+ return null; // TODO(b/123100824): implement
+ }
+
private void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
Rect anchorBounds, IAutofillWindowPresenter presenter) {
final View anchor = findView(id);
@@ -1769,8 +1893,8 @@
}
if (callback != null) {
- if (id.isVirtual()) {
- callback.onAutofillEvent(anchor, id.getVirtualChildId(),
+ if (id.isVirtualInt()) {
+ callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
AutofillCallback.EVENT_INPUT_SHOWN);
} else {
callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
@@ -1896,7 +2020,7 @@
failedIds.add(id);
continue;
}
- if (id.isVirtual()) {
+ if (id.isVirtualInt()) {
if (virtualValues == null) {
// Most likely there will be just one view with virtual children.
virtualValues = new ArrayMap<>(1);
@@ -1907,7 +2031,7 @@
valuesByParent = new SparseArray<>(5);
virtualValues.put(view, valuesByParent);
}
- valuesByParent.put(id.getVirtualChildId(), value);
+ valuesByParent.put(id.getVirtualChildIntId(), value);
} else {
// Mark the view as to be autofilled with 'value'
if (mLastAutofilledData == null) {
@@ -2142,8 +2266,8 @@
}
if (callback != null) {
- if (id.isVirtual()) {
- callback.onAutofillEvent(anchor, id.getVirtualChildId(),
+ if (id.isVirtualInt()) {
+ callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
AutofillCallback.EVENT_INPUT_HIDDEN);
} else {
callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
@@ -2169,8 +2293,8 @@
}
if (callback != null) {
- if (id.isVirtual()) {
- callback.onAutofillEvent(anchor, id.getVirtualChildId(),
+ if (id.isVirtualInt()) {
+ callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
AutofillCallback.EVENT_INPUT_UNAVAILABLE);
} else {
callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
@@ -2296,6 +2420,11 @@
}
}
+ /** @hide */
+ public static String getSmartSuggestionModeToString(@SmartSuggestionMode int flags) {
+ return flagsToString(AutofillManager.class, "FLAG_SMART_SUGGESTION_", flags);
+ }
+
@GuardedBy("mLock")
private boolean isActiveLocked() {
return mState == STATE_ACTIVE;
@@ -2972,7 +3101,6 @@
@Override
public Rect getViewCoordinates(@NonNull AutofillId id) {
- // TODO(b/111330312): use handler / callback?
final AutofillManager afm = mAfm.get();
if (afm == null) return null;
diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java
index 2d2987a..1928613 100644
--- a/core/java/android/view/contentcapture/ContentCaptureContext.java
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.java
@@ -22,6 +22,7 @@
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
@@ -84,9 +85,9 @@
// Fields below are set by app on Builder
private final @Nullable Bundle mExtras;
private final @Nullable Uri mUri;
+ private final @Nullable String mAction;
// Fields below are set by server when the session starts
- // TODO(b/111276913): create new object for taskId + componentName / reuse on other places
private final @Nullable ComponentName mComponentName;
private final int mTaskId;
private final int mDisplayId;
@@ -102,10 +103,12 @@
mHasClientContext = true;
mExtras = clientContext.mExtras;
mUri = clientContext.mUri;
+ mAction = clientContext.mAction;
} else {
mHasClientContext = false;
mExtras = null;
mUri = null;
+ mAction = null;
}
mComponentName = Preconditions.checkNotNull(componentName);
mTaskId = taskId;
@@ -117,13 +120,14 @@
mHasClientContext = true;
mExtras = builder.mExtras;
mUri = builder.mUri;
+ mAction = builder.mAction;
mComponentName = null;
mTaskId = mFlags = mDisplayId = 0;
}
/**
- * Gets the (optional) extras set by the app.
+ * Gets the (optional) extras set by the app (through {@link Builder#setExtras(Bundle)}).
*
* <p>It can be used to provide vendor-specific data that can be modified and examined.
*
@@ -136,7 +140,7 @@
}
/**
- * Gets the (optional) URI set by the app.
+ * Gets the (optional) URI set by the app (through {@link Builder#setUri(Uri)}).
*
* @hide
*/
@@ -147,6 +151,17 @@
}
/**
+ * Gets the (optional) action set by the app (through {@link Builder#setAction(String)}).
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public String getAction() {
+ return mAction;
+ }
+
+ /**
* Gets the id of the {@link TaskInfo task} associated with this context.
*
* @hide
@@ -213,6 +228,8 @@
public static final class Builder {
private Bundle mExtras;
private Uri mUri;
+ private boolean mDestroyed;
+ private String mAction;
/**
* Sets extra options associated with this context.
@@ -221,11 +238,13 @@
*
* @param extras extra options.
* @return this builder.
+ *
+ * @throws IllegalStateException if {@link #build()} was already called.
*/
@NonNull
public Builder setExtras(@NonNull Bundle extras) {
- // TODO(b/111276913): check build just once / throw exception / test / document
mExtras = Preconditions.checkNotNull(extras);
+ throwIfDestroyed();
return this;
}
@@ -236,23 +255,51 @@
*
* @param uri URI associated with this context.
* @return this builder.
+ *
+ * @throws IllegalStateException if {@link #build()} was already called.
*/
@NonNull
public Builder setUri(@NonNull Uri uri) {
- // TODO(b/111276913): check build just once / throw exception / test / document
mUri = Preconditions.checkNotNull(uri);
+ throwIfDestroyed();
+ return this;
+ }
+
+ /**
+ * Sets an {@link Intent#getAction() intent action} associated with this context.
+ *
+ * @param action intent action
+ *
+ * @return this builder
+ *
+ * @throws IllegalStateException if {@link #build()} was already called.
+ */
+ @NonNull
+ public Builder setAction(@NonNull String action) {
+ mAction = Preconditions.checkNotNull(action);
+ throwIfDestroyed();
return this;
}
/**
* Builds the {@link ContentCaptureContext}.
+ *
+ * @throws IllegalStateException if {@link #build()} was already called or no call to either
+ * {@link #setExtras(Bundle)}, {@link #setAction(String)}, or {@link #setUri(Uri)} was made.
+ *
+ * @return the built {@code ContentCaptureContext}
*/
public ContentCaptureContext build() {
- // TODO(b/111276913): check build just once / throw exception / test / document
- // TODO(b/111276913): make sure it at least one property (uri / extras) / test /
- // throw exception / documment
+ throwIfDestroyed();
+ Preconditions.checkState(mExtras != null || mUri != null || mAction != null,
+ "Must call setUri() or setExtras() or setUri() before calling build()");
+ mDestroyed = true;
return new ContentCaptureContext(this);
}
+
+ private void throwIfDestroyed() {
+ Preconditions.checkState(!mDestroyed, "Already called #build()");
+ }
}
/**
@@ -277,6 +324,10 @@
// NOTE: cannot dump because it could contain PII
pw.print(", hasUri");
}
+ if (mAction != null) {
+ // NOTE: cannot dump because it could contain PII
+ pw.print(", hasAction");
+ }
}
@Override
@@ -297,6 +348,10 @@
// NOTE: cannot print because it could contain PII
builder.append(", hasUri");
}
+ if (mAction != null) {
+ // NOTE: cannot print because it could contain PII
+ builder.append(", hasAction");
+ }
return builder.append(']').toString();
}
@@ -310,6 +365,7 @@
parcel.writeInt(mHasClientContext ? 1 : 0);
if (mHasClientContext) {
parcel.writeParcelable(mUri, flags);
+ parcel.writeString(mAction);
parcel.writeBundle(mExtras);
}
parcel.writeParcelable(mComponentName, flags);
@@ -329,12 +385,14 @@
final ContentCaptureContext clientContext;
if (hasClientContext) {
+ // Must reconstruct the client context using the Builder API
final Builder builder = new Builder();
final Uri uri = parcel.readParcelable(null);
+ final String action = parcel.readString();
final Bundle extras = parcel.readBundle();
if (uri != null) builder.setUri(uri);
+ if (action != null) builder.setAction(action);
if (extras != null) builder.setExtras(extras);
- // Must reconstruct the client context using the Builder API
clientContext = new ContentCaptureContext(builder);
} else {
clientContext = null;
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index 43963c3..a6d4472 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -15,6 +15,8 @@
*/
package android.view.contentcapture;
+import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -267,8 +269,7 @@
pw.print(", parentSessionId="); pw.print(mParentSessionId);
}
if (mText != null) {
- // Cannot print content because could have PII
- pw.print(", text="); pw.print(mText.length()); pw.print("_chars");
+ pw.print(", text="); pw.println(getSanitizedString(mText));
}
}
@@ -293,6 +294,9 @@
}
string.append(", id=").append(mNode.getAutofillId());
}
+ if (mText != null) {
+ string.append(", text=").append(getSanitizedString(mText));
+ }
return string.append(']').toString();
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureHelper.java b/core/java/android/view/contentcapture/ContentCaptureHelper.java
new file mode 100644
index 0000000..508880f
--- /dev/null
+++ b/core/java/android/view/contentcapture/ContentCaptureHelper.java
@@ -0,0 +1,40 @@
+/*
+ * 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.view.contentcapture;
+
+import android.annotation.Nullable;
+
+/**
+ * Helpe class for this package.
+ */
+final class ContentCaptureHelper {
+
+ // TODO(b/121044306): define a way to dynamically set them(for example, using settings?)
+ static final boolean VERBOSE = false;
+ static final boolean DEBUG = true; // STOPSHIP if not set to false
+
+ /**
+ * Used to log text that could contain PII.
+ */
+ @Nullable
+ public static String getSanitizedString(@Nullable CharSequence text) {
+ return text == null ? null : text.length() + "_chars";
+ }
+
+ private ContentCaptureHelper() {
+ throw new UnsupportedOperationException("contains only static methods");
+ }
+}
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 413f1a5..b9017b3 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -15,6 +15,8 @@
*/
package android.view.contentcapture;
+import static android.view.contentcapture.ContentCaptureHelper.VERBOSE;
+
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.NonNull;
@@ -57,10 +59,6 @@
*/
private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
- // TODO(b/121044306): define a way to dynamically set them(for example, using settings?)
- static final boolean VERBOSE = false;
- static final boolean DEBUG = true; // STOPSHIP if not set to false
-
private final Object mLock = new Object();
@GuardedBy("mLock")
@@ -191,13 +189,19 @@
}
/**
- * Called by the ap to request the Content Capture service to remove user-data associated with
+ * Called by the app to request the Content Capture service to remove user-data associated with
* some context.
*
* @param request object specifying what user data should be removed.
*/
public void removeUserData(@NonNull UserDataRemovalRequest request) {
- //TODO(b/111276913): implement
+ Preconditions.checkNotNull(request);
+
+ try {
+ mService.removeUserData(mContext.getUserId(), request);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
/** @hide */
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index b620ab1..6ed2d80 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -15,8 +15,8 @@
*/
package android.view.contentcapture;
-import static android.view.contentcapture.ContentCaptureManager.DEBUG;
-import static android.view.contentcapture.ContentCaptureManager.VERBOSE;
+import static android.view.contentcapture.ContentCaptureHelper.DEBUG;
+import static android.view.contentcapture.ContentCaptureHelper.VERBOSE;
import android.annotation.CallSuper;
import android.annotation.IntDef;
@@ -246,7 +246,7 @@
public final void destroy() {
synchronized (mLock) {
if (mDestroyed) {
- Log.e(TAG, "destroy(" + mId + "): already destroyed");
+ if (DEBUG) Log.d(TAG, "destroy(" + mId + "): already destroyed");
return;
}
mDestroyed = true;
@@ -352,14 +352,14 @@
* @throws IllegalArgumentException if {@code virtualIds} is empty
*/
public final void notifyViewsDisappeared(@NonNull AutofillId hostId,
- @NonNull int[] virtualIds) {
- Preconditions.checkArgument(!hostId.isVirtual(), "parent cannot be virtual");
+ @NonNull long[] virtualIds) {
+ Preconditions.checkArgument(hostId.isNonVirtual(), "parent cannot be virtual");
Preconditions.checkArgument(!ArrayUtils.isEmpty(virtualIds), "virtual ids cannot be empty");
if (!isContentCaptureEnabled()) return;
// TODO(b/123036895): use a internalNotifyViewsDisappeared that optimizes how the event is
// parcelized
- for (int id : virtualIds) {
+ for (long id : virtualIds) {
internalNotifyViewDisappeared(new AutofillId(hostId, id, getIdAsInt()));
}
}
@@ -405,9 +405,9 @@
*
* @throws IllegalArgumentException if the {@code parentId} is a virtual child id.
*/
- public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, int virtualChildId) {
+ public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, long virtualChildId) {
Preconditions.checkNotNull(parentId);
- Preconditions.checkArgument(!parentId.isVirtual(), "virtual ids cannot have children");
+ Preconditions.checkArgument(parentId.isNonVirtual(), "virtual ids cannot have children");
return new AutofillId(parentId, virtualChildId, getIdAsInt());
}
@@ -423,7 +423,7 @@
*/
@NonNull
public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId,
- int virtualId) {
+ long virtualId) {
return new ViewNode.ViewStructureImpl(parentId, virtualId, getIdAsInt());
}
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index be9c00f..51aea16 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -19,6 +19,7 @@
import android.content.ComponentName;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.UserDataRemovalRequest;
import android.os.IBinder;
import com.android.internal.os.IResultReceiver;
@@ -56,4 +57,9 @@
* provided {@code Bundle} with key "{@code EXTRA}".
*/
void getReceiverServiceComponentName(int userId, in IResultReceiver result);
+
+ /**
+ * Requests the removal of user data for the provided {@code userId}.
+ */
+ void removeUserData(int userId, in UserDataRemovalRequest request);
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 103d7e6..9e99c88 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -20,8 +20,9 @@
import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
-import static android.view.contentcapture.ContentCaptureManager.DEBUG;
-import static android.view.contentcapture.ContentCaptureManager.VERBOSE;
+import static android.view.contentcapture.ContentCaptureHelper.DEBUG;
+import static android.view.contentcapture.ContentCaptureHelper.VERBOSE;
+import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -269,6 +270,7 @@
private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
final int eventType = event.getType();
+ if (VERBOSE) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
if (!handleHasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED) {
// TODO(b/120494182): comment when this could happen (dialogs?)
Log.v(TAG, "handleSendEvent(" + getDebugState() + ", "
@@ -276,12 +278,16 @@
+ "): session not started yet");
return;
}
- if (VERBOSE) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
+ if (mDisabled.get()) {
+ // This happens when the event was queued in the handler before the sesison was ready,
+ // then handleSessionStarted() returned and set it as disabled - we need to drop it,
+ // otherwise it will keep triggering handleScheduleFlush()
+ if (VERBOSE) Log.v(TAG, "handleSendEvent(): ignoring when disabled");
+ return;
+ }
if (mEvents == null) {
if (VERBOSE) {
- Log.v(TAG, "handleSendEvent(" + getDebugState() + ", "
- + ContentCaptureEvent.getTypeAsString(eventType)
- + "): creating buffer for " + MAX_BUFFER_SIZE + " events");
+ Log.v(TAG, "handleSendEvent(): creating buffer for " + MAX_BUFFER_SIZE + " events");
}
mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
}
@@ -296,8 +302,8 @@
if (lastEvent.getType() == TYPE_VIEW_TEXT_CHANGED
&& lastEvent.getId().equals(event.getId())) {
if (VERBOSE) {
- Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text = "
- + event.getText());
+ Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text="
+ + getSanitizedString(event.getText()));
}
lastEvent.setText(event.getText());
addEvent = false;
@@ -365,8 +371,20 @@
}
private void handleScheduleFlush(@FlushReason int reason, boolean checkExisting) {
+ if (VERBOSE) {
+ Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason)
+ + ", checkExisting=" + checkExisting);
+ }
if (!handleHasStarted()) {
- Log.v(TAG, "handleScheduleFlush(" + getDebugState() + "): session not started yet");
+ if (VERBOSE) Log.v(TAG, "handleScheduleFlush(): session not started yet");
+ return;
+ }
+
+ if (mDisabled.get()) {
+ // Should not be called on this state, as handleSendEvent checks.
+ // But we rather add one if check and log than re-schedule and keep the session alive...
+ Log.e(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): should not be called "
+ + "when disabled. events=" + (mEvents == null ? null : mEvents.size()));
return;
}
if (checkExisting && mHandler.hasMessages(MSG_FLUSH)) {
@@ -375,8 +393,7 @@
}
mNextFlush = System.currentTimeMillis() + FLUSHING_FREQUENCY_MS;
if (VERBOSE) {
- Log.v(TAG, "handleScheduleFlush(" + getDebugState()
- + ", reason=" + getflushReasonAsString(reason) + "): scheduled to flush in "
+ Log.v(TAG, "handleScheduleFlush(): scheduled to flush in "
+ FLUSHING_FREQUENCY_MS + "ms: " + TimeUtils.logTimeOfDay(mNextFlush));
}
mHandler.sendMessageDelayed(
@@ -395,11 +412,16 @@
private void handleForceFlush(@FlushReason int reason) {
if (mEvents == null) return;
+ if (mDisabled.get()) {
+ Log.e(TAG, "handleForceFlush(" + getDebugState(reason) + "): should not be when "
+ + "disabled");
+ return;
+ }
+
if (mDirectServiceInterface == null) {
if (VERBOSE) {
- Log.v(TAG, "handleForceFlush(" + getDebugState()
- + ", reason=" + getflushReasonAsString(reason)
- + "): hold your horses, client not ready: " + mEvents);
+ Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, "
+ + "client not ready: " + mEvents);
}
if (!mHandler.hasMessages(MSG_FLUSH)) {
handleScheduleFlush(reason, /* checkExisting= */ false);
@@ -410,8 +432,7 @@
final int numberEvents = mEvents.size();
final String reasonString = getflushReasonAsString(reason);
if (DEBUG) {
- Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState()
- + ". Reason: " + reasonString);
+ Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason));
}
// Logs reason, size, max size, idle timeout
final String logRecord = "r=" + reasonString + " s=" + numberEvents
@@ -592,7 +613,14 @@
: "act:" + mComponentName.flattenToShortString();
}
+ @NonNull
private String getDebugState() {
- return getActivityName() + " (state=" + getStateAsString(mState) + ")";
+ return getActivityName() + " [state=" + getStateAsString(mState) + ", disabled="
+ + mDisabled.get() + "]";
+ }
+
+ @NonNull
+ private String getDebugState(@FlushReason int reason) {
+ return getDebugState() + ", reason=" + getflushReasonAsString(reason);
}
}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl
similarity index 73%
copy from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
copy to core/java/android/view/contentcapture/UserDataRemovalRequest.aidl
index 62a8c48..fbe47e0 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 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,10 +14,6 @@
* limitations under the License.
*/
-package com.android.testing.alarmservice;
+package android.view.contentcapture;
-interface Alarm {
- int prepare();
- int setAlarmAndWait(long timeoutMills);
- int done();
-}
\ No newline at end of file
+parcelable UserDataRemovalRequest;
diff --git a/core/java/android/view/contentcapture/UserDataRemovalRequest.java b/core/java/android/view/contentcapture/UserDataRemovalRequest.java
index 0261b70..8ee63ef 100644
--- a/core/java/android/view/contentcapture/UserDataRemovalRequest.java
+++ b/core/java/android/view/contentcapture/UserDataRemovalRequest.java
@@ -17,10 +17,15 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.app.ActivityThread;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.IntArray;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
import java.util.List;
/**
@@ -29,8 +34,35 @@
*/
public final class UserDataRemovalRequest implements Parcelable {
- private UserDataRemovalRequest(Builder builder) {
- // TODO(b/111276913): implement
+ private final String mPackageName;
+
+ private final boolean mForEverything;
+ private ArrayList<UriRequest> mUriRequests;
+
+ private UserDataRemovalRequest(@NonNull Builder builder) {
+ mPackageName = ActivityThread.currentActivityThread().getApplication().getPackageName();
+ mForEverything = builder.mForEverything;
+ if (builder.mUris != null) {
+ final int size = builder.mUris.size();
+ mUriRequests = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ mUriRequests.add(new UriRequest(builder.mUris.get(i),
+ builder.mRecursive.get(i) == 1));
+ }
+ }
+ }
+
+ private UserDataRemovalRequest(@NonNull Parcel parcel) {
+ mPackageName = parcel.readString();
+ mForEverything = parcel.readBoolean();
+ if (!mForEverything) {
+ final int size = parcel.readInt();
+ mUriRequests = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ mUriRequests.add(new UriRequest((Uri) parcel.readValue(null),
+ parcel.readBoolean()));
+ }
+ }
}
/**
@@ -40,9 +72,7 @@
@SystemApi
@NonNull
public String getPackageName() {
- // TODO(b/111276913): implement
- // TODO(b/111276913): make sure it's set on system_service so it cannot be faked by app
- return null;
+ return mPackageName;
}
/**
@@ -52,8 +82,7 @@
*/
@SystemApi
public boolean isForEverything() {
- // TODO(b/111276913): implement
- return false;
+ return mForEverything;
}
/**
@@ -64,8 +93,7 @@
@SystemApi
@NonNull
public List<UriRequest> getUriRequests() {
- // TODO(b/111276913): implement
- return null;
+ return mUriRequests;
}
/**
@@ -73,6 +101,12 @@
*/
public static final class Builder {
+ private boolean mForEverything;
+ private ArrayList<Uri> mUris;
+ private IntArray mRecursive;
+
+ private boolean mDestroyed;
+
/**
* Requests servive to remove all user data associated with the app's package.
*
@@ -80,7 +114,12 @@
*/
@NonNull
public Builder forEverything() {
- // TODO(b/111276913): implement
+ throwIfDestroyed();
+ if (mUris != null) {
+ throw new IllegalStateException("Already added Uris");
+ }
+
+ mForEverything = true;
return this;
}
@@ -94,7 +133,19 @@
* @return this builder
*/
public Builder addUri(@NonNull Uri uri, boolean recursive) {
- // TODO(b/111276913): implement
+ throwIfDestroyed();
+ if (mForEverything) {
+ throw new IllegalStateException("Already is for everything");
+ }
+ Preconditions.checkNotNull(uri);
+
+ if (mUris == null) {
+ mUris = new ArrayList<>();
+ mRecursive = new IntArray();
+ }
+
+ mUris.add(uri);
+ mRecursive.add(recursive ? 1 : 0);
return this;
}
@@ -103,8 +154,16 @@
*/
@NonNull
public UserDataRemovalRequest build() {
- // TODO(b/111276913): implement / unit test / check built / document exceptions
- return null;
+ throwIfDestroyed();
+
+ Preconditions.checkState(mForEverything || mUris != null);
+
+ mDestroyed = true;
+ return new UserDataRemovalRequest(this);
+ }
+
+ private void throwIfDestroyed() {
+ Preconditions.checkState(!mDestroyed, "Already destroyed!");
}
}
@@ -115,7 +174,17 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
- // TODO(b/111276913): implement
+ parcel.writeString(mPackageName);
+ parcel.writeBoolean(mForEverything);
+ if (!mForEverything) {
+ final int size = mUriRequests.size();
+ parcel.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ final UriRequest request = mUriRequests.get(i);
+ parcel.writeValue(request.getUri());
+ parcel.writeBoolean(request.isRecursive());
+ }
+ }
}
public static final Parcelable.Creator<UserDataRemovalRequest> CREATOR =
@@ -123,8 +192,7 @@
@Override
public UserDataRemovalRequest createFromParcel(Parcel parcel) {
- // TODO(b/111276913): implement
- return null;
+ return new UserDataRemovalRequest(parcel);
}
@Override
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index cbc946b..0cabafa 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -617,7 +617,7 @@
}
@VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
- public ViewStructureImpl(@NonNull AutofillId parentId, int virtualId, int sessionId) {
+ public ViewStructureImpl(@NonNull AutofillId parentId, long virtualId, int sessionId) {
mNode.mParentAutofillId = Preconditions.checkNotNull(parentId);
mNode.mAutofillId = new AutofillId(parentId, virtualId, sessionId);
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 86c5f18..0cb1800 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -27,6 +27,7 @@
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -922,19 +923,6 @@
}
}
- /**
- * Returns a list of VR InputMethod currently installed.
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
- public List<InputMethodInfo> getVrInputMethodList() {
- try {
- return mService.getVrInputMethodList();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
public List<InputMethodInfo> getEnabledInputMethodList() {
try {
return mService.getEnabledInputMethodList();
@@ -1894,7 +1882,15 @@
/**
* Notify the event when the user tapped or clicked the text view.
+ *
+ * @param view {@link View} which is being clicked.
+ * @see InputMethodService#onViewClicked(boolean)
+ * @deprecated The semantics of this method can never be defined well for composite {@link View}
+ * that works as a giant "Canvas", which can host its own UI hierarchy and sub focus
+ * state. {@link android.webkit.WebView} is a good example. Application / IME
+ * developers should not rely on this method.
*/
+ @Deprecated
public void viewClicked(View view) {
// Re-dispatch if there is a context mismatch.
final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
@@ -2494,14 +2490,58 @@
* @param subtype A new input method subtype to switch.
* @return true if the current subtype was successfully switched. When the specified subtype is
* null, this method returns false.
+ * @deprecated If the calling process is an IME, use
+ * {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}, which
+ * does not require any permission as long as the caller is the current IME.
+ * If the calling process is some privileged app that already has
+ * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission, just
+ * directly update {@link Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE}.
*/
+ @Deprecated
@RequiresPermission(WRITE_SECURE_SETTINGS)
public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
- try {
- return mService.setCurrentInputMethodSubtype(subtype);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ Log.w(TAG, "System process should not call setCurrentInputMethodSubtype() because "
+ + "almost always it is a bug under multi-user / multi-profile environment. "
+ + "Consider directly interacting with InputMethodManagerService "
+ + "via LocalServices.");
+ return false;
}
+ if (subtype == null) {
+ // See the JavaDoc. This is how this method has worked.
+ return false;
+ }
+ final Context fallbackContext = ActivityThread.currentApplication();
+ if (fallbackContext == null) {
+ return false;
+ }
+ if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ final ContentResolver contentResolver = fallbackContext.getContentResolver();
+ final String imeId = Settings.Secure.getString(contentResolver,
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+ if (ComponentName.unflattenFromString(imeId) == null) {
+ // Null or invalid IME ID format.
+ return false;
+ }
+ final List<InputMethodSubtype> enabledSubtypes;
+ try {
+ enabledSubtypes = mService.getEnabledInputMethodSubtypeList(imeId, true);
+ } catch (RemoteException e) {
+ return false;
+ }
+ final int numSubtypes = enabledSubtypes.size();
+ for (int i = 0; i < numSubtypes; ++i) {
+ final InputMethodSubtype enabledSubtype = enabledSubtypes.get(i);
+ if (enabledSubtype.equals(subtype)) {
+ Settings.Secure.putInt(contentResolver,
+ Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, enabledSubtype.hashCode());
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -2642,7 +2682,13 @@
*
* @param imiId Id of InputMethodInfo which additional input method subtypes will be added to.
* @param subtypes subtypes will be added as additional subtypes of the current input method.
+ * @deprecated For IMEs that have already implemented features like customizable/downloadable
+ * keyboard layouts/languages, please start migration to other approaches. One idea
+ * would be exposing only one unified {@link InputMethodSubtype} then implement
+ * IME's own language switching mechanism within that unified subtype. The support
+ * of "Additional Subtype" may be completely dropped in a future version of Android.
*/
+ @Deprecated
public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
try {
mService.setAdditionalInputMethodSubtypes(imiId, subtypes);
diff --git a/core/java/android/view/inspector/InspectableProperty.java b/core/java/android/view/inspector/InspectableProperty.java
index 355ff1d..f859521 100644
--- a/core/java/android/view/inspector/InspectableProperty.java
+++ b/core/java/android/view/inspector/InspectableProperty.java
@@ -106,6 +106,7 @@
/**
* One entry in an enumeration packed into a primitive {int}.
*
+ * @see IntEnumMapping
* @hide
*/
@Target({TYPE})
diff --git a/core/java/android/view/inspector/IntEnumMapping.java b/core/java/android/view/inspector/IntEnumMapping.java
new file mode 100644
index 0000000..147bb46
--- /dev/null
+++ b/core/java/android/view/inspector/IntEnumMapping.java
@@ -0,0 +1,102 @@
+/*
+ * 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.view.inspector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import java.util.Objects;
+
+/**
+ * Maps the values of an {@code int} property to strings for properties that encode an enumeration.
+ *
+ * An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper}
+ * for flag values packed into primitive {@code int} properties.
+ *
+ * This class is an immutable wrapper for {@link SparseArray}, and must be constructed by a
+ * {@link Builder}.
+ *
+ * @see PropertyMapper#mapIntEnum(String, int, IntEnumMapping)
+ */
+public final class IntEnumMapping {
+ private final SparseArray<String> mValues;
+
+ /**
+ * Get the name for the given property value
+ *
+ * @param value The value of the property
+ * @return The name of the value in the enumeration, or null if no value is defined
+ */
+ @Nullable
+ public String get(int value) {
+ return mValues.get(value);
+ }
+
+ /**
+ * Create a new instance from a builder.
+ *
+ * This constructor is private, use {@link Builder#build()} instead.
+ *
+ * @param builder A builder to create from
+ */
+ private IntEnumMapping(Builder builder) {
+ mValues = builder.mValues.clone();
+ }
+
+ /**
+ * A builder for {@link IntEnumMapping}.
+ */
+ public static final class Builder {
+ @NonNull
+ private SparseArray<String> mValues;
+ private boolean mMustCloneValues = false;
+
+ public Builder() {
+ mValues = new SparseArray<>();
+ }
+
+ /**
+ * Add a new enumerated value.
+ *
+ * @param name The string name of the enumeration value
+ * @param value The {@code int} value of the enumeration value
+ * @return This builder
+ */
+ @NonNull
+ public Builder addValue(@NonNull String name, int value) {
+ // Save an allocation, only re-clone if the builder is used again after building
+ if (mMustCloneValues) {
+ mValues = mValues.clone();
+ }
+
+ mValues.put(value, Objects.requireNonNull(name));
+ return this;
+ }
+
+ /**
+ * Build a new {@link IntEnumMapping} from this builder.
+ *
+ * @return A new mapping
+ */
+ @NonNull
+ public IntEnumMapping build() {
+ mMustCloneValues = true;
+ return new IntEnumMapping(this);
+ }
+ }
+}
diff --git a/core/java/android/view/inspector/IntFlagMapping.java b/core/java/android/view/inspector/IntFlagMapping.java
index 8f7dfd5..2409081 100644
--- a/core/java/android/view/inspector/IntFlagMapping.java
+++ b/core/java/android/view/inspector/IntFlagMapping.java
@@ -21,10 +21,11 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Objects;
import java.util.Set;
/**
- * Maps the values of an {@code int} property to arrays of string for properties that encode flags.
+ * Maps the values of an {@code int} property to sets of string for properties that encode flags.
*
* An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper}
* for flag values packed into primitive {@code int} properties.
@@ -45,7 +46,7 @@
* Get an array of the names of enabled flags for a given property value.
*
* @param value The value of the property
- * @return The names of the enabled flags
+ * @return The names of the enabled flags, empty if no flags enabled
*/
@NonNull
public Set<String> get(int value) {
@@ -136,7 +137,7 @@
private final int mMask;
private Flag(@NonNull String name, int target, int mask) {
- mName = name;
+ mName = Objects.requireNonNull(name);
mTarget = target;
mMask = mask;
}
diff --git a/core/java/android/view/inspector/PropertyMapper.java b/core/java/android/view/inspector/PropertyMapper.java
index e20582b..00b18d1 100644
--- a/core/java/android/view/inspector/PropertyMapper.java
+++ b/core/java/android/view/inspector/PropertyMapper.java
@@ -18,7 +18,6 @@
import android.annotation.AttrRes;
import android.annotation.NonNull;
-import android.util.SparseArray;
/**
* An interface for mapping the string names of inspectable properties to integer identifiers.
@@ -155,14 +154,14 @@
int mapIntEnum(
@NonNull String name,
@AttrRes int attributeId,
- @NonNull SparseArray<String> mapping);
+ @NonNull IntEnumMapping mapping);
/**
* Map a string name to an integer ID for a flag set packed into an int property.
*
* @param name The name of the property
* @param attributeId If the property is from an XML attribute, the resource ID of the property
- * @param mapping A mapping from int to an array of strings
+ * @param mapping A mapping from int to a set of strings
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
*/
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 9ab963e..a5b7c62 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -78,6 +78,9 @@
*/
public final class TextClassifierImpl implements TextClassifier {
+ /** @hide */
+ public static final String ACTIONS_INTENTS = "actions-intents";
+
private static final String LOG_TAG = DEFAULT_LOG_TAG;
private static final boolean DEBUG = false;
@@ -567,6 +570,7 @@
// TODO: Make this configurable.
final float foreignTextThreshold = typeCount == 0 ? 0.5f : 0.7f;
boolean isPrimaryAction = true;
+ final ArrayList<Intent> sourceIntents = new ArrayList<>();
for (LabeledIntent labeledIntent : IntentFactory.create(
mContext, classifiedText, isForeignText(classifiedText, foreignTextThreshold),
referenceTime, highestScoringResult)) {
@@ -586,9 +590,15 @@
isPrimaryAction = false;
}
builder.addAction(action);
+ sourceIntents.add(labeledIntent.getIntent());
}
- return builder.setId(createId(text, start, end)).build();
+ final Bundle extras = new Bundle();
+ extras.putParcelableArrayList(ACTIONS_INTENTS, sourceIntents);
+
+ return builder.setId(createId(text, start, end))
+ .setExtras(extras)
+ .build();
}
private boolean isForeignText(String text, float threshold) {
diff --git a/core/java/android/webkit/WebViewRenderer.java b/core/java/android/webkit/WebViewRenderer.java
index 5328254..fc38cd9 100644
--- a/core/java/android/webkit/WebViewRenderer.java
+++ b/core/java/android/webkit/WebViewRenderer.java
@@ -16,6 +16,8 @@
package android.webkit;
+import android.annotation.SystemApi;
+
/**
* WebViewRenderer provides an opaque handle to a {@link WebView} renderer.
*/
@@ -40,6 +42,7 @@
* This class cannot be created by applications.
* @hide
*/
+ @SystemApi
public WebViewRenderer() {
}
}
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 29b3b3c..3a1c457 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -150,7 +150,7 @@
}
try {
- sZygote = Process.zygoteProcess.startChildZygote(
+ sZygote = Process.ZYGOTE_PROCESS.startChildZygote(
"com.android.internal.os.WebViewZygoteInit",
"webview_zygote",
Process.WEBVIEW_ZYGOTE_UID,
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1085e5d..780fe8d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5117,7 +5117,7 @@
* @attr ref android.R.styleable#TextView_scrollHorizontally
* @see #setHorizontallyScrolling(boolean)
*/
- public final boolean isHorizontallyScrolling() {
+ public final boolean isHorizontallyScrollable() {
return mHorizontallyScrolling;
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index b4d8322..30137e38 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -103,7 +103,9 @@
* binding to every ChooserTargetService implementation.
*/
// TODO(b/121287573): Replace with a system flag (setprop?)
- private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = false;
+ private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
+ private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
+
// TODO(b/121287224): Re-evaluate this limit
private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
@@ -136,6 +138,7 @@
private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2;
private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 3;
+ private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 4;
private final Handler mChooserHandler = new Handler() {
@Override
@@ -182,6 +185,9 @@
mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
resultInfo.resultTargets);
}
+ break;
+
+ case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
sendVoiceChoicesIfNeeded();
mChooserListAdapter.setShowServiceTargets(true);
break;
@@ -630,6 +636,7 @@
// Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
// for direct share targets. After ShareSheet is refactored we should use the
// ShareShortcutInfos directly.
+ boolean resultMessageSent = false;
for (int i = 0; i < driList.size(); i++) {
List<ChooserTarget> chooserTargets = new ArrayList<>();
for (int j = 0; j < resultList.size(); j++) {
@@ -646,6 +653,13 @@
msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
mChooserHandler.sendMessage(msg);
+ resultMessageSent = true;
+ }
+
+ if (resultMessageSent) {
+ final Message msg = Message.obtain();
+ msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
+ mChooserHandler.sendMessage(msg);
}
});
}
@@ -1178,13 +1192,17 @@
mTargetsNeedPruning = true;
}
}
+
if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
if (DEBUG) {
Log.d(TAG, "querying direct share targets from ShortcutManager");
}
queryDirectShareTargets(this);
- } else {
- if (DEBUG) Log.d(TAG, "List built querying services");
+ }
+ if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
+ if (DEBUG) {
+ Log.d(TAG, "List built querying services");
+ }
queryTargetServices(this);
}
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 5088cca..b85488f 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -151,4 +151,19 @@
*/
void getActiveServiceSupportedActions(in List<String> voiceActions,
in IVoiceActionCheckCallback callback);
+
+ /**
+ * Sets the transcribed voice to the given string.
+ */
+ void setTranscription(String transcription);
+
+ /**
+ * Indicates that the transcription session is finished.
+ */
+ void clearTranscription(boolean immediate);
+
+ /**
+ * Sets the voice state indication based upon the given value.
+ */
+ void setVoiceState(int state);
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl
index 87749d2..674ad5b 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl
@@ -26,4 +26,20 @@
* Called when a voice session is hidden.
*/
void onVoiceSessionHidden();
+
+ /**
+ * Called when voice assistant transcription has been updated to the given string.
+ */
+ void onTranscriptionUpdate(in String transcription);
+
+ /**
+ * Called when voice transcription is completed.
+ */
+ void onTranscriptionComplete(in boolean immediate);
+
+ /**
+ * Called when the voice assistant's state has changed. Values are from
+ * VoiceInteractionService's VOICE_STATE* constants.
+ */
+ void onVoiceStateChange(in int state);
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index a27dbea..18c4b46 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -74,6 +74,16 @@
private static final boolean LOG_INOTIFY = false;
+ protected static final String SUPPORTED_QUERY_ARGS = joinNewline(
+ DocumentsContract.QUERY_ARG_DISPLAY_NAME,
+ DocumentsContract.QUERY_ARG_FILE_SIZE_OVER,
+ DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER,
+ DocumentsContract.QUERY_ARG_MIME_TYPES);
+
+ private static String joinNewline(String... args) {
+ return TextUtils.join("\n", args);
+ }
+
private String[] mDefaultProjection;
@GuardedBy("mObservers")
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index c4aa1d7..a691a24 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -25,7 +25,13 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
- * Log all the things.
+ * Writes sysui_multi_event records to the system event log.
+ *
+ * Prefer the methods write(LogMaker), or count() or histogram(). Replace legacy methods with
+ * their current equivalents when the opportunity arises.
+ *
+ * This class is a lightweight dependency barrier - it is cheap and easy to construct.
+ * Logging is also cheap, so it is not normally necessary to move logging off of the UI thread.
*
* @hide
*/
@@ -52,6 +58,7 @@
public static final int VIEW_UNKNOWN = MetricsEvent.VIEW_UNKNOWN;
public static final int LOGTAG = EventLogTags.SYSUI_MULTI_ACTION;
+ /** Write an event log record, consisting of content.serialize(). */
@UnsupportedAppUsage
public void write(LogMaker content) {
if (content.getType() == MetricsEvent.TYPE_UNKNOWN) {
@@ -60,128 +67,145 @@
saveLog(content);
}
+ /** Add an integer value to the monotonically increasing counter with the given name. */
+ public void count(String name, int value) {
+ saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
+ .setCounterName(name)
+ .setCounterValue(value));
+ }
+
+ /** Increment the bucket with the integer label on the histogram with the given name. */
+ public void histogram(String name, int bucket) {
+ // see LogHistogram in system/core/libmetricslogger/metrics_logger.cpp
+ saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
+ .setCounterName(name)
+ .setCounterBucket(bucket)
+ .setCounterValue(1));
+ }
+
+ /* Legacy logging methods follow. These are all simple shorthands and can be replaced
+ * with an equivalent write(). */
+
+ /** Logs an OPEN event on the category.
+ * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_OPEN)) */
public void visible(int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
throw new IllegalArgumentException("Must define metric category");
}
- EventLogTags.writeSysuiViewVisibility(category, 100);
saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_OPEN));
}
+ /** Logs a CLOSE event on the category.
+ * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_CLOSE)) */
public void hidden(int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
throw new IllegalArgumentException("Must define metric category");
}
- EventLogTags.writeSysuiViewVisibility(category, 0);
saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_CLOSE));
}
- public void visibility(int category, boolean visibile)
+ /** Logs an OPEN or CLOSE event on the category, depending on visible.
+ * Equivalent to write(new LogMaker(category)
+ * .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)) */
+ public void visibility(int category, boolean visible)
throws IllegalArgumentException {
- if (visibile) {
+ if (visible) {
visible(category);
} else {
hidden(category);
}
}
+ /** Logs an OPEN or CLOSE event on the category, depending on vis.
+ * Equivalent to write(new LogMaker(category)
+ .setType(vis == View.VISIBLE ?
+ MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)) */
public void visibility(int category, int vis)
throws IllegalArgumentException {
visibility(category, vis == View.VISIBLE);
}
+ /** Logs an ACTION event on the category.
+ * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION)) */
public void action(int category) {
- EventLogTags.writeSysuiAction(category, "");
saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION));
}
+ /** Logs an ACTION event on the category.
+ * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION)
+ .setSubtype(value) */
public void action(int category, int value) {
- EventLogTags.writeSysuiAction(category, Integer.toString(value));
saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION).setSubtype(value));
}
+ /** Logs an ACTION event on the category.
+ * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION)
+ .setSubtype(value ? 1 : 0) */
public void action(int category, boolean value) {
- EventLogTags.writeSysuiAction(category, Boolean.toString(value));
saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION).setSubtype(value ? 1 : 0));
}
+ /** Logs an ACTION event on the category.
+ * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION)
+ .setPackageName(value ? 1 : 0) */
public void action(int category, String pkg) {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
throw new IllegalArgumentException("Must define metric category");
}
- EventLogTags.writeSysuiAction(category, pkg);
saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION).setPackageName(pkg));
}
- /** Add an integer value to the monotonically increasing counter with the given name. */
- public void count(String name, int value) {
- EventLogTags.writeSysuiCount(name, value);
- saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
- .setCounterName(name)
- .setCounterValue(value));
- }
-
- /** Increment the bucket with the integer label on the histogram with the given name. */
- public void histogram(String name, int bucket) {
- // see LogHistogram in system/core/libmetricslogger/metrics_logger.cpp
- EventLogTags.writeSysuiHistogram(name, bucket);
- saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
- .setCounterName(name)
- .setCounterBucket(bucket)
- .setCounterValue(1));
- }
-
- /** @deprecated use {@link #visible(int)} */
+ /** @deprecated because untestable; use {@link #visible(int)} */
@Deprecated
public static void visible(Context context, int category) throws IllegalArgumentException {
getLogger().visible(category);
}
- /** @deprecated use {@link #hidden(int)} */
+ /** @deprecated because untestable; use {@link #hidden(int)} */
@Deprecated
public static void hidden(Context context, int category) throws IllegalArgumentException {
getLogger().hidden(category);
}
- /** @deprecated use {@link #visibility(int, boolean)} */
+ /** @deprecated because untestable; use {@link #visibility(int, boolean)} */
@Deprecated
public static void visibility(Context context, int category, boolean visibile)
throws IllegalArgumentException {
getLogger().visibility(category, visibile);
}
- /** @deprecated use {@link #visibility(int, int)} */
+ /** @deprecated because untestable; use {@link #visibility(int, int)} */
@Deprecated
public static void visibility(Context context, int category, int vis)
throws IllegalArgumentException {
visibility(context, category, vis == View.VISIBLE);
}
- /** @deprecated use {@link #action(int)} */
+ /** @deprecated because untestable; use {@link #action(int)} */
@Deprecated
public static void action(Context context, int category) {
getLogger().action(category);
}
- /** @deprecated use {@link #action(int, int)} */
+ /** @deprecated because untestable; use {@link #action(int, int)} */
@Deprecated
public static void action(Context context, int category, int value) {
getLogger().action(category, value);
}
- /** @deprecated use {@link #action(int, boolean)} */
+ /** @deprecated because untestable; use {@link #action(int, boolean)} */
@Deprecated
public static void action(Context context, int category, boolean value) {
getLogger().action(category, value);
}
- /** @deprecated use {@link #write(LogMaker)} */
+ /** @deprecated because untestable; use {@link #write(LogMaker)} */
@Deprecated
public static void action(LogMaker content) {
getLogger().write(content);
}
- /** @deprecated use {@link #action(int, String)} */
+ /** @deprecated because untestable; use {@link #action(int, String)} */
@Deprecated
public static void action(Context context, int category, String pkg) {
getLogger().action(category, pkg);
@@ -189,7 +213,7 @@
/**
* Add an integer value to the monotonically increasing counter with the given name.
- * @deprecated use {@link #count(String, int)}
+ * @deprecated because untestable; use {@link #count(String, int)}
*/
@Deprecated
public static void count(Context context, String name, int value) {
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index fd03b3f..da8605e 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -28,6 +28,7 @@
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.Network;
+import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.os.Parcel;
import android.os.Parcelable;
@@ -104,6 +105,7 @@
public boolean allowIPv4;
public boolean allowIPv6;
public Network[] underlyingNetworks;
+ public ProxyInfo proxyInfo;
public void updateAllowedFamilies(InetAddress address) {
if (address instanceof Inet4Address) {
@@ -164,6 +166,7 @@
out.writeInt(allowIPv4 ? 1 : 0);
out.writeInt(allowIPv6 ? 1 : 0);
out.writeTypedArray(underlyingNetworks, flags);
+ out.writeParcelable(proxyInfo, flags);
}
public static final Parcelable.Creator<VpnConfig> CREATOR =
@@ -189,6 +192,7 @@
config.allowIPv4 = in.readInt() != 0;
config.allowIPv6 = in.readInt() != 0;
config.underlyingNetworks = in.createTypedArray(Network.CREATOR);
+ config.proxyInfo = in.readParcelable(null);
return config;
}
@@ -220,6 +224,7 @@
.append(", allowIPv4=").append(allowIPv4)
.append(", allowIPv6=").append(allowIPv6)
.append(", underlyingNetworks=").append(Arrays.toString(underlyingNetworks))
+ .append(", proxyInfo=").append(proxyInfo.toString())
.append("}")
.toString();
}
diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/com/android/internal/net/VpnInfo.java
index a676dac..b1a41287 100644
--- a/core/java/com/android/internal/net/VpnInfo.java
+++ b/core/java/com/android/internal/net/VpnInfo.java
@@ -32,11 +32,11 @@
@Override
public String toString() {
- return "VpnInfo{" +
- "ownerUid=" + ownerUid +
- ", vpnIface='" + vpnIface + '\'' +
- ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\'' +
- '}';
+ return "VpnInfo{"
+ + "ownerUid=" + ownerUid
+ + ", vpnIface='" + vpnIface + '\''
+ + ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\''
+ + '}';
}
@Override
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 17cc6af..534361e 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -87,6 +87,10 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.location.gnssmetrics.GnssMetrics;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FastXmlSerializer;
@@ -187,18 +191,19 @@
private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
@VisibleForTesting
- protected KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
+ protected KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader =
+ new KernelCpuUidUserSysTimeReader(true);
@VisibleForTesting
protected KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
@VisibleForTesting
- protected KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
- new KernelUidCpuFreqTimeReader();
+ protected KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader =
+ new KernelCpuUidFreqTimeReader(true);
@VisibleForTesting
- protected KernelUidCpuActiveTimeReader mKernelUidCpuActiveTimeReader =
- new KernelUidCpuActiveTimeReader();
+ protected KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader =
+ new KernelCpuUidActiveTimeReader(true);
@VisibleForTesting
- protected KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader =
- new KernelUidCpuClusterTimeReader();
+ protected KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader =
+ new KernelCpuUidClusterTimeReader(true);
@VisibleForTesting
protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
@@ -248,9 +253,9 @@
/** Last time that RPM stats were updated by updateRpmStatsLocked. */
private long mLastRpmStatsUpdateTimeMs = -RPM_STATS_UPDATE_FREQ_MS;
/**
- * Use a queue to delay removing UIDs from {@link KernelUidCpuTimeReader},
- * {@link KernelUidCpuActiveTimeReader}, {@link KernelUidCpuClusterTimeReader},
- * {@link KernelUidCpuFreqTimeReader} and from the Kernel.
+ * Use a queue to delay removing UIDs from {@link KernelCpuUidUserSysTimeReader},
+ * {@link KernelCpuUidActiveTimeReader}, {@link KernelCpuUidClusterTimeReader},
+ * {@link KernelCpuUidFreqTimeReader} and from the Kernel.
*
* Isolated and invalid UID info must be removed to conserve memory. However, STATSD and
* Batterystats both need to access UID cpu time. To resolve this race condition, only
@@ -281,22 +286,22 @@
void remove() {
if (startUid == endUid) {
- mKernelUidCpuTimeReader.removeUid(startUid);
- mKernelUidCpuFreqTimeReader.removeUid(startUid);
+ mCpuUidUserSysTimeReader.removeUid(startUid);
+ mCpuUidFreqTimeReader.removeUid(startUid);
if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
- mKernelUidCpuActiveTimeReader.removeUid(startUid);
- mKernelUidCpuClusterTimeReader.removeUid(startUid);
+ mCpuUidActiveTimeReader.removeUid(startUid);
+ mCpuUidClusterTimeReader.removeUid(startUid);
}
if (mKernelSingleUidTimeReader != null) {
mKernelSingleUidTimeReader.removeUid(startUid);
}
mNumUidsRemoved++;
} else if (startUid < endUid) {
- mKernelUidCpuFreqTimeReader.removeUidsInRange(startUid, endUid);
- mKernelUidCpuTimeReader.removeUidsInRange(startUid, endUid);
+ mCpuUidFreqTimeReader.removeUidsInRange(startUid, endUid);
+ mCpuUidUserSysTimeReader.removeUidsInRange(startUid, endUid);
if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
- mKernelUidCpuActiveTimeReader.removeUidsInRange(startUid, endUid);
- mKernelUidCpuClusterTimeReader.removeUidsInRange(startUid, endUid);
+ mCpuUidActiveTimeReader.removeUidsInRange(startUid, endUid);
+ mCpuUidClusterTimeReader.removeUidsInRange(startUid, endUid);
}
if (mKernelSingleUidTimeReader != null) {
mKernelSingleUidTimeReader.removeUidsInRange(startUid, endUid);
@@ -496,7 +501,7 @@
}
final SparseArray<long[]> allUidCpuFreqTimesMs =
- mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs();
+ mCpuUidFreqTimeReader.getAllUidCpuFreqTimeMs();
// If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
// compute deltas since it might result in mis-attributing cpu times to wrong states.
if (mIsPerProcessStateCpuDataStale) {
@@ -553,16 +558,16 @@
return false;
}
if (mCpuFreqs == null) {
- mCpuFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
+ mCpuFreqs = mCpuUidFreqTimeReader.readFreqs(mPowerProfile);
}
if (mCpuFreqs != null) {
mKernelSingleUidTimeReader = new KernelSingleUidTimeReader(mCpuFreqs.length);
} else {
- mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable();
+ mPerProcStateCpuTimesAvailable = mCpuUidFreqTimeReader.allUidTimesAvailable();
return false;
}
}
- mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable()
+ mPerProcStateCpuTimesAvailable = mCpuUidFreqTimeReader.allUidTimesAvailable()
&& mKernelSingleUidTimeReader.singleUidCpuTimesAvailable();
return true;
}
@@ -11926,7 +11931,7 @@
}
if (mCpuFreqs == null) {
- mCpuFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
+ mCpuFreqs = mCpuUidFreqTimeReader.readFreqs(mPowerProfile);
}
// Calculate the wakelocks we have to distribute amongst. The system is excluded as it is
@@ -11952,12 +11957,12 @@
// When the battery is not on, we don't attribute the cpu times to any timers but we still
// need to take the snapshots.
if (!onBattery) {
- mKernelUidCpuTimeReader.readDelta(null);
- mKernelUidCpuFreqTimeReader.readDelta(null);
+ mCpuUidUserSysTimeReader.readDelta(null);
+ mCpuUidFreqTimeReader.readDelta(null);
mNumAllUidCpuTimeReads += 2;
if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
- mKernelUidCpuActiveTimeReader.readDelta(null);
- mKernelUidCpuClusterTimeReader.readDelta(null);
+ mCpuUidActiveTimeReader.readDelta(null);
+ mCpuUidClusterTimeReader.readDelta(null);
mNumAllUidCpuTimeReads += 2;
}
for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) {
@@ -11967,7 +11972,7 @@
}
mUserInfoProvider.refreshUserIds();
- final SparseLongArray updatedUids = mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()
+ final SparseLongArray updatedUids = mCpuUidFreqTimeReader.perClusterTimesAvailable()
? null : new SparseLongArray();
readKernelUidCpuTimesLocked(partialTimersToConsider, updatedUids, onBattery);
// updatedUids=null means /proc/uid_time_in_state provides snapshots of per-cluster cpu
@@ -12084,18 +12089,20 @@
final int numWakelocks = partialTimers == null ? 0 : partialTimers.size();
final long startTimeMs = mClocks.uptimeMillis();
- mKernelUidCpuTimeReader.readDelta((uid, userTimeUs, systemTimeUs) -> {
+ mCpuUidUserSysTimeReader.readDelta((uid, timesUs) -> {
+ long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
+
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
// This could happen if the isolated uid mapping was removed before that process
// was actually killed.
- mKernelUidCpuTimeReader.removeUid(uid);
+ mCpuUidUserSysTimeReader.removeUid(uid);
Slog.d(TAG, "Got readings for an isolated uid with no mapping: " + uid);
return;
}
if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
Slog.d(TAG, "Got readings for an invalid user's uid " + uid);
- mKernelUidCpuTimeReader.removeUid(uid);
+ mCpuUidUserSysTimeReader.removeUid(uid);
return;
}
final Uid u = getUidStatsLocked(uid);
@@ -12189,21 +12196,21 @@
public void readKernelUidCpuFreqTimesLocked(@Nullable ArrayList<StopwatchTimer> partialTimers,
boolean onBattery, boolean onBatteryScreenOff) {
final boolean perClusterTimesAvailable =
- mKernelUidCpuFreqTimeReader.perClusterTimesAvailable();
+ mCpuUidFreqTimeReader.perClusterTimesAvailable();
final int numWakelocks = partialTimers == null ? 0 : partialTimers.size();
final int numClusters = mPowerProfile.getNumCpuClusters();
mWakeLockAllocationsUs = null;
final long startTimeMs = mClocks.uptimeMillis();
- mKernelUidCpuFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> {
+ mCpuUidFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
- mKernelUidCpuFreqTimeReader.removeUid(uid);
+ mCpuUidFreqTimeReader.removeUid(uid);
Slog.d(TAG, "Got freq readings for an isolated uid with no mapping: " + uid);
return;
}
if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
Slog.d(TAG, "Got freq readings for an invalid user's uid " + uid);
- mKernelUidCpuFreqTimeReader.removeUid(uid);
+ mCpuUidFreqTimeReader.removeUid(uid);
return;
}
final Uid u = getUidStatsLocked(uid);
@@ -12307,16 +12314,16 @@
@VisibleForTesting
public void readKernelUidCpuActiveTimesLocked(boolean onBattery) {
final long startTimeMs = mClocks.uptimeMillis();
- mKernelUidCpuActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> {
+ mCpuUidActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
- mKernelUidCpuActiveTimeReader.removeUid(uid);
+ mCpuUidActiveTimeReader.removeUid(uid);
Slog.w(TAG, "Got active times for an isolated uid with no mapping: " + uid);
return;
}
if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
Slog.w(TAG, "Got active times for an invalid user's uid " + uid);
- mKernelUidCpuActiveTimeReader.removeUid(uid);
+ mCpuUidActiveTimeReader.removeUid(uid);
return;
}
final Uid u = getUidStatsLocked(uid);
@@ -12336,16 +12343,16 @@
@VisibleForTesting
public void readKernelUidCpuClusterTimesLocked(boolean onBattery) {
final long startTimeMs = mClocks.uptimeMillis();
- mKernelUidCpuClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> {
+ mCpuUidClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
- mKernelUidCpuClusterTimeReader.removeUid(uid);
+ mCpuUidClusterTimeReader.removeUid(uid);
Slog.w(TAG, "Got cluster times for an isolated uid with no mapping: " + uid);
return;
}
if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
Slog.w(TAG, "Got cluster times for an invalid user's uid " + uid);
- mKernelUidCpuClusterTimeReader.removeUid(uid);
+ mCpuUidClusterTimeReader.removeUid(uid);
return;
}
final Uid u = getUidStatsLocked(uid);
@@ -13344,7 +13351,7 @@
private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = false;
private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true;
private static final long DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS = 5_000;
- private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 10_000;
+ private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 1_000;
private static final long DEFAULT_UID_REMOVE_DELAY_MS = 5L * 60L * 1000L;
private static final long DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS = 600_000;
private static final long DEFAULT_BATTERY_LEVEL_COLLECTION_DELAY_MS = 300_000;
@@ -13357,7 +13364,9 @@
public boolean TRACK_CPU_TIMES_BY_PROC_STATE = DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE;
public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME;
public long PROC_STATE_CPU_TIMES_READ_DELAY_MS = DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS;
- public long KERNEL_UID_READERS_THROTTLE_TIME = DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME;
+ /* Do not set default value for KERNEL_UID_READERS_THROTTLE_TIME. Need to trigger an
+ * update when startObserving. */
+ public long KERNEL_UID_READERS_THROTTLE_TIME;
public long UID_REMOVE_DELAY_MS = DEFAULT_UID_REMOVE_DELAY_MS;
public long EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS
= DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS;
@@ -13464,11 +13473,11 @@
private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) {
KERNEL_UID_READERS_THROTTLE_TIME = newTimeMs;
if (oldTimeMs != newTimeMs) {
- mKernelUidCpuTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
- mKernelUidCpuFreqTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
- mKernelUidCpuActiveTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
- mKernelUidCpuClusterTimeReader
- .setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
+ mCpuUidUserSysTimeReader.setThrottle(KERNEL_UID_READERS_THROTTLE_TIME);
+ mCpuUidFreqTimeReader.setThrottle(KERNEL_UID_READERS_THROTTLE_TIME);
+ mCpuUidActiveTimeReader.setThrottle(KERNEL_UID_READERS_THROTTLE_TIME);
+ mCpuUidClusterTimeReader
+ .setThrottle(KERNEL_UID_READERS_THROTTLE_TIME);
}
}
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 051a96c..6454352 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -53,6 +53,7 @@
public static final boolean DETAILED_TRACKING_DEFAULT = true;
public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 100;
public static final int MAX_BINDER_CALL_STATS_COUNT_DEFAULT = 5000;
+ private static final String DEBUG_ENTRY_PREFIX = "__DEBUG_";
private static class OverflowBinder extends Binder {}
@@ -347,7 +348,7 @@
callStat.callingUid = uid;
callStat.recordedCallCount = 1;
callStat.callCount = 1;
- callStat.methodName = "__DEBUG_" + variableName;
+ callStat.methodName = DEBUG_ENTRY_PREFIX + variableName;
callStat.latencyMicros = value;
return callStat;
}
@@ -398,6 +399,10 @@
final List<ExportedCallStat> exportedCallStats = getExportedCallStats();
exportedCallStats.sort(BinderCallsStats::compareByCpuDesc);
for (ExportedCallStat e : exportedCallStats) {
+ if (e.methodName.startsWith(DEBUG_ENTRY_PREFIX)) {
+ // Do not dump debug entries.
+ continue;
+ }
sb.setLength(0);
sb.append(" ")
.append(packageMap.mapUid(e.callingUid))
diff --git a/core/java/com/android/internal/os/KernelCpuProcReader.java b/core/java/com/android/internal/os/KernelCpuProcReader.java
deleted file mode 100644
index c233ea8..0000000
--- a/core/java/com/android/internal/os/KernelCpuProcReader.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import android.os.StrictMode;
-import android.os.SystemClock;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-
-/**
- * Reads cpu time proc files with throttling (adjustable interval).
- *
- * KernelCpuProcReader is implemented as singletons for built-in kernel proc files. Get___Instance()
- * method will return corresponding reader instance. In order to prevent frequent GC,
- * KernelCpuProcReader reuses a {@link ByteBuffer} to store data read from proc files.
- *
- * A KernelCpuProcReader instance keeps an error counter. When the number of read errors within that
- * instance accumulates to 5, this instance will reject all further read requests.
- *
- * Each KernelCpuProcReader instance also has a throttler. Throttle interval can be adjusted via
- * {@link #setThrottleInterval(long)} method. Default throttle interval is 3000ms. If current
- * timestamp based on {@link SystemClock#elapsedRealtime()} is less than throttle interval from
- * the last read timestamp, {@link #readBytes()} will return previous result.
- *
- * A KernelCpuProcReader instance is thread-unsafe. Caller needs to hold a lock on this object while
- * accessing its instance methods or digesting the return values.
- */
-public class KernelCpuProcReader {
- private static final String TAG = "KernelCpuProcReader";
- private static final int ERROR_THRESHOLD = 5;
- // Throttle interval in milliseconds
- private static final long DEFAULT_THROTTLE_INTERVAL = 3000L;
- private static final int MAX_BUFFER_SIZE = 1024 * 1024;
- private static final String PROC_UID_FREQ_TIME = "/proc/uid_cpupower/time_in_state";
- private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_cpupower/concurrent_active_time";
- private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_cpupower/concurrent_policy_time";
-
- private static final KernelCpuProcReader mFreqTimeReader = new KernelCpuProcReader(
- PROC_UID_FREQ_TIME);
- private static final KernelCpuProcReader mActiveTimeReader = new KernelCpuProcReader(
- PROC_UID_ACTIVE_TIME);
- private static final KernelCpuProcReader mClusterTimeReader = new KernelCpuProcReader(
- PROC_UID_CLUSTER_TIME);
-
- public static KernelCpuProcReader getFreqTimeReaderInstance() {
- return mFreqTimeReader;
- }
-
- public static KernelCpuProcReader getActiveTimeReaderInstance() {
- return mActiveTimeReader;
- }
-
- public static KernelCpuProcReader getClusterTimeReaderInstance() {
- return mClusterTimeReader;
- }
-
- private int mErrors;
- private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
- private long mLastReadTime = Long.MIN_VALUE;
- private final Path mProc;
- private byte[] mBuffer = new byte[8 * 1024];
- private int mContentSize;
-
- @VisibleForTesting
- public KernelCpuProcReader(String procFile) {
- mProc = Paths.get(procFile);
- }
-
- /**
- * Reads all bytes from the corresponding proc file.
- *
- * If elapsed time since last call to this method is less than the throttle interval, it will
- * return previous result. When IOException accumulates to 5, it will always return null. This
- * method is thread-unsafe, so is the return value. Caller needs to hold a lock on this
- * object while calling this method and digesting its return value.
- *
- * @return a {@link ByteBuffer} containing all bytes from the proc file.
- */
- public ByteBuffer readBytes() {
- if (mErrors >= ERROR_THRESHOLD) {
- return null;
- }
- if (SystemClock.elapsedRealtime() < mLastReadTime + mThrottleInterval) {
- if (mContentSize > 0) {
- return ByteBuffer.wrap(mBuffer, 0, mContentSize).asReadOnlyBuffer()
- .order(ByteOrder.nativeOrder());
- }
- return null;
- }
- mLastReadTime = SystemClock.elapsedRealtime();
- mContentSize = 0;
- final int oldMask = StrictMode.allowThreadDiskReadsMask();
- try (InputStream in = Files.newInputStream(mProc)) {
- int numBytes = 0;
- int curr;
- while ((curr = in.read(mBuffer, numBytes, mBuffer.length - numBytes)) >= 0) {
- numBytes += curr;
- if (numBytes == mBuffer.length) {
- // Hit the limit. Resize mBuffer.
- if (mBuffer.length == MAX_BUFFER_SIZE) {
- mErrors++;
- Slog.e(TAG, "Proc file is too large: " + mProc);
- return null;
- }
- mBuffer = Arrays.copyOf(mBuffer,
- Math.min(mBuffer.length << 1, MAX_BUFFER_SIZE));
- }
- }
- mContentSize = numBytes;
- return ByteBuffer.wrap(mBuffer, 0, mContentSize).asReadOnlyBuffer()
- .order(ByteOrder.nativeOrder());
- } catch (NoSuchFileException | FileNotFoundException e) {
- // Happens when the kernel does not provide this file. Not a big issue. Just log it.
- mErrors++;
- Slog.w(TAG, "File not exist: " + mProc);
- } catch (IOException e) {
- mErrors++;
- Slog.e(TAG, "Error reading: " + mProc, e);
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
- }
- return null;
- }
-
- /**
- * Sets the throttle interval. Set to 0 will disable throttling. Thread-unsafe, holding a lock
- * on this object is recommended.
- *
- * @param throttleInterval throttle interval in milliseconds
- */
- public void setThrottleInterval(long throttleInterval) {
- if (throttleInterval >= 0) {
- mThrottleInterval = throttleInterval;
- }
- }
-}
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index 7021b57..e6d044f 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -177,6 +177,9 @@
* The file contains a monotonically increasing count of time for a single boot. This class
* maintains the previous results of a call to {@link #readDelta} in order to provide a proper
* delta.
+ *
+ * The second parameter of the callback is a long[] with 2 elements, [user time in us, system
+ * time in us].
*/
public static class KernelCpuUidUserSysTimeReader extends KernelCpuUidTimeReader<long[]> {
private static final String REMOVE_UID_PROC_FILE = "/proc/uid_cputime/remove_uid_range";
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
index ad62852..3c43a11 100644
--- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -16,7 +16,6 @@
package com.android.internal.os;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-import static com.android.internal.os.KernelUidCpuFreqTimeReader.UID_TIMES_PROC_FILE;
import android.annotation.NonNull;
import android.util.Slog;
@@ -34,11 +33,12 @@
@VisibleForTesting(visibility = PACKAGE)
public class KernelSingleUidTimeReader {
- private final String TAG = KernelUidCpuFreqTimeReader.class.getName();
- private final boolean DBG = false;
+ private static final String TAG = KernelSingleUidTimeReader.class.getName();
+ private static final boolean DBG = false;
- private final String PROC_FILE_DIR = "/proc/uid/";
- private final String PROC_FILE_NAME = "/time_in_state";
+ private static final String PROC_FILE_DIR = "/proc/uid/";
+ private static final String PROC_FILE_NAME = "/time_in_state";
+ private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
@VisibleForTesting
public static final int TOTAL_READ_ERROR_COUNT = 5;
diff --git a/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java
deleted file mode 100644
index bd8a67a..0000000
--- a/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2017 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.internal.os;
-
-import android.annotation.Nullable;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.nio.ByteBuffer;
-import java.nio.IntBuffer;
-import java.util.function.Consumer;
-
-/**
- * Reads binary proc file /proc/uid_cpupower/concurrent_active_time and reports CPU active time to
- * BatteryStats to compute {@link PowerProfile#POWER_CPU_ACTIVE}.
- *
- * concurrent_active_time is an array of u32's in the following format:
- * [n, uid0, time0a, time0b, ..., time0n,
- * uid1, time1a, time1b, ..., time1n,
- * uid2, time2a, time2b, ..., time2n, etc.]
- * where n is the total number of cpus (num_possible_cpus)
- * ...
- * timeXn means the CPU time that a UID X spent running concurrently with n other processes.
- * The file contains a monotonically increasing count of time for a single boot. This class
- * maintains the previous results of a call to {@link #readDelta} in order to provide a
- * proper delta.
- *
- * This class uses a throttler to reject any {@link #readDelta} call within
- * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
- * which has a shorter throttle interval and returns cached result from last read when the request
- * is throttled.
- *
- * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
- * caller has its own view of delta.
- */
-public class KernelUidCpuActiveTimeReader extends
- KernelUidCpuTimeReaderBase<KernelUidCpuActiveTimeReader.Callback> {
- private static final String TAG = KernelUidCpuActiveTimeReader.class.getSimpleName();
-
- private final KernelCpuProcReader mProcReader;
- private SparseArray<Double> mLastUidCpuActiveTimeMs = new SparseArray<>();
- private int mCores;
-
- public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
- /**
- * Notifies when new data is available.
- *
- * @param uid uid int
- * @param cpuActiveTimeMs cpu active time spent by this uid in milliseconds
- */
- void onUidCpuActiveTime(int uid, long cpuActiveTimeMs);
- }
-
- public KernelUidCpuActiveTimeReader() {
- mProcReader = KernelCpuProcReader.getActiveTimeReaderInstance();
- }
-
- @VisibleForTesting
- public KernelUidCpuActiveTimeReader(KernelCpuProcReader procReader) {
- mProcReader = procReader;
- }
-
- @Override
- protected void readDeltaImpl(@Nullable Callback callback) {
- readImpl((buf) -> {
- int uid = buf.get();
- double activeTime = sumActiveTime(buf);
- if (activeTime > 0) {
- double delta = activeTime - mLastUidCpuActiveTimeMs.get(uid, 0.0);
- if (delta > 0) {
- mLastUidCpuActiveTimeMs.put(uid, activeTime);
- if (callback != null) {
- callback.onUidCpuActiveTime(uid, (long) delta);
- }
- } else if (delta < 0) {
- Slog.e(TAG, "Negative delta from active time proc: " + delta);
- }
- }
- });
- }
-
- public void readAbsolute(Callback callback) {
- readImpl((buf) -> {
- int uid = buf.get();
- double activeTime = sumActiveTime(buf);
- if (activeTime > 0) {
- callback.onUidCpuActiveTime(uid, (long) activeTime);
- }
- });
- }
-
- private double sumActiveTime(IntBuffer buffer) {
- double sum = 0;
- boolean corrupted = false;
- for (int j = 1; j <= mCores; j++) {
- int time = buffer.get();
- if (time < 0) {
- // Even if error happens, we still need to continue reading.
- // Buffer cannot be skipped.
- Slog.e(TAG, "Negative time from active time proc: " + time);
- corrupted = true;
- } else {
- sum += (double) time * 10 / j; // Unit is 10ms.
- }
- }
- return corrupted ? -1 : sum;
- }
-
- /**
- * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last
- * seen results while processing the buffer, while readAbsolute returns the absolute value read
- * from the buffer without storing. So readImpl contains the common logic of the two, leaving
- * the difference to a processUid function.
- *
- * @param processUid the callback function to process the uid entry in the buffer.
- */
- private void readImpl(Consumer<IntBuffer> processUid) {
- synchronized (mProcReader) {
- final ByteBuffer bytes = mProcReader.readBytes();
- if (bytes == null || bytes.remaining() <= 4) {
- // Error already logged in mProcReader.
- return;
- }
- if ((bytes.remaining() & 3) != 0) {
- Slog.wtf(TAG,
- "Cannot parse active time proc bytes to int: " + bytes.remaining());
- return;
- }
- final IntBuffer buf = bytes.asIntBuffer();
- final int cores = buf.get();
- if (mCores != 0 && cores != mCores) {
- Slog.wtf(TAG, "Cpu active time wrong # cores: " + cores);
- return;
- }
- mCores = cores;
- if (cores <= 0 || buf.remaining() % (cores + 1) != 0) {
- Slog.wtf(TAG,
- "Cpu active time format error: " + buf.remaining() + " / " + (cores
- + 1));
- return;
- }
- int numUids = buf.remaining() / (cores + 1);
- for (int i = 0; i < numUids; i++) {
- processUid.accept(buf);
- }
- if (DEBUG) {
- Slog.d(TAG, "Read uids: " + numUids);
- }
- }
- }
-
- public void removeUid(int uid) {
- mLastUidCpuActiveTimeMs.delete(uid);
- }
-
- public void removeUidsInRange(int startUid, int endUid) {
- mLastUidCpuActiveTimeMs.put(startUid, null);
- mLastUidCpuActiveTimeMs.put(endUid, null);
- final int firstIndex = mLastUidCpuActiveTimeMs.indexOfKey(startUid);
- final int lastIndex = mLastUidCpuActiveTimeMs.indexOfKey(endUid);
- mLastUidCpuActiveTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
- }
-}
diff --git a/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java
deleted file mode 100644
index 3cbfaea..0000000
--- a/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2017 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.internal.os;
-
-import android.annotation.Nullable;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.nio.ByteBuffer;
-import java.nio.IntBuffer;
-import java.util.function.Consumer;
-
-/**
- * Reads binary proc file /proc/uid_cpupower/concurrent_policy_time and reports CPU cluster times
- * to BatteryStats to compute cluster power. See
- * {@link PowerProfile#getAveragePowerForCpuCluster(int)}.
- *
- * concurrent_policy_time is an array of u32's in the following format:
- * [n, x0, ..., xn, uid0, time0a, time0b, ..., time0n,
- * uid1, time1a, time1b, ..., time1n,
- * uid2, time2a, time2b, ..., time2n, etc.]
- * where n is the number of policies
- * xi is the number cpus on a particular policy
- * Each uidX is followed by x0 time entries corresponding to the time UID X spent on cluster0
- * running concurrently with 0, 1, 2, ..., x0 - 1 other processes, then followed by x1, ..., xn
- * time entries.
- *
- * The file contains a monotonically increasing count of time for a single boot. This class
- * maintains the previous results of a call to {@link #readDelta} in order to provide a
- * proper delta.
- *
- * This class uses a throttler to reject any {@link #readDelta} call within
- * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
- * which has a shorter throttle interval and returns cached result from last read when the request
- * is throttled.
- *
- * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
- * caller has its own view of delta.
- */
-public class KernelUidCpuClusterTimeReader extends
- KernelUidCpuTimeReaderBase<KernelUidCpuClusterTimeReader.Callback> {
- private static final String TAG = KernelUidCpuClusterTimeReader.class.getSimpleName();
-
- private final KernelCpuProcReader mProcReader;
- private SparseArray<double[]> mLastUidPolicyTimeMs = new SparseArray<>();
-
- private int mNumClusters = -1;
- private int mNumCores;
- private int[] mNumCoresOnCluster;
-
- private double[] mCurTime; // Reuse to avoid GC.
- private long[] mDeltaTime; // Reuse to avoid GC.
- private long[] mCurTimeRounded; // Reuse to avoid GC.
-
- public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
- /**
- * Notifies when new data is available.
- *
- * @param uid uid int
- * @param cpuClusterTimeMs an array of times spent by this uid on corresponding clusters.
- * The array index is the cluster index.
- */
- void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs);
- }
-
- public KernelUidCpuClusterTimeReader() {
- mProcReader = KernelCpuProcReader.getClusterTimeReaderInstance();
- }
-
- @VisibleForTesting
- public KernelUidCpuClusterTimeReader(KernelCpuProcReader procReader) {
- mProcReader = procReader;
- }
-
- @Override
- protected void readDeltaImpl(@Nullable Callback cb) {
- readImpl((buf) -> {
- int uid = buf.get();
- double[] lastTimes = mLastUidPolicyTimeMs.get(uid);
- if (lastTimes == null) {
- lastTimes = new double[mNumClusters];
- mLastUidPolicyTimeMs.put(uid, lastTimes);
- }
- if (!sumClusterTime(buf, mCurTime)) {
- return;
- }
- boolean valid = true;
- boolean notify = false;
- for (int i = 0; i < mNumClusters; i++) {
- mDeltaTime[i] = (long) (mCurTime[i] - lastTimes[i]);
- if (mDeltaTime[i] < 0) {
- Slog.e(TAG, "Negative delta from cluster time proc: " + mDeltaTime[i]);
- valid = false;
- }
- notify |= mDeltaTime[i] > 0;
- }
- if (notify && valid) {
- System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
- if (cb != null) {
- cb.onUidCpuPolicyTime(uid, mDeltaTime);
- }
- }
- });
- }
-
- public void readAbsolute(Callback callback) {
- readImpl((buf) -> {
- int uid = buf.get();
- if (sumClusterTime(buf, mCurTime)) {
- for (int i = 0; i < mNumClusters; i++) {
- mCurTimeRounded[i] = (long) mCurTime[i];
- }
- callback.onUidCpuPolicyTime(uid, mCurTimeRounded);
- }
- });
- }
-
- private boolean sumClusterTime(IntBuffer buffer, double[] clusterTime) {
- boolean valid = true;
- for (int i = 0; i < mNumClusters; i++) {
- clusterTime[i] = 0;
- for (int j = 1; j <= mNumCoresOnCluster[i]; j++) {
- int time = buffer.get();
- if (time < 0) {
- Slog.e(TAG, "Negative time from cluster time proc: " + time);
- valid = false;
- }
- clusterTime[i] += (double) time * 10 / j; // Unit is 10ms.
- }
- }
- return valid;
- }
-
- /**
- * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last
- * seen results while processing the buffer, while readAbsolute returns the absolute value read
- * from the buffer without storing. So readImpl contains the common logic of the two, leaving
- * the difference to a processUid function.
- *
- * @param processUid the callback function to process the uid entry in the buffer.
- */
- private void readImpl(Consumer<IntBuffer> processUid) {
- synchronized (mProcReader) {
- ByteBuffer bytes = mProcReader.readBytes();
- if (bytes == null || bytes.remaining() <= 4) {
- // Error already logged in mProcReader.
- return;
- }
- if ((bytes.remaining() & 3) != 0) {
- Slog.wtf(TAG,
- "Cannot parse cluster time proc bytes to int: " + bytes.remaining());
- return;
- }
- IntBuffer buf = bytes.asIntBuffer();
- final int numClusters = buf.get();
- if (numClusters <= 0) {
- Slog.wtf(TAG, "Cluster time format error: " + numClusters);
- return;
- }
- if (mNumClusters == -1) {
- mNumClusters = numClusters;
- }
- if (buf.remaining() < numClusters) {
- Slog.wtf(TAG, "Too few data left in the buffer: " + buf.remaining());
- return;
- }
- if (mNumCores <= 0) {
- if (!readCoreInfo(buf, numClusters)) {
- return;
- }
- } else {
- buf.position(buf.position() + numClusters);
- }
-
- if (buf.remaining() % (mNumCores + 1) != 0) {
- Slog.wtf(TAG,
- "Cluster time format error: " + buf.remaining() + " / " + (mNumCores
- + 1));
- return;
- }
- int numUids = buf.remaining() / (mNumCores + 1);
-
- for (int i = 0; i < numUids; i++) {
- processUid.accept(buf);
- }
- if (DEBUG) {
- Slog.d(TAG, "Read uids: " + numUids);
- }
- }
- }
-
- // Returns if it has read valid info.
- private boolean readCoreInfo(IntBuffer buf, int numClusters) {
- int numCores = 0;
- int[] numCoresOnCluster = new int[numClusters];
- for (int i = 0; i < numClusters; i++) {
- numCoresOnCluster[i] = buf.get();
- numCores += numCoresOnCluster[i];
- }
- if (numCores <= 0) {
- Slog.e(TAG, "Invalid # cores from cluster time proc file: " + numCores);
- return false;
- }
- mNumCores = numCores;
- mNumCoresOnCluster = numCoresOnCluster;
- mCurTime = new double[numClusters];
- mDeltaTime = new long[numClusters];
- mCurTimeRounded = new long[numClusters];
- return true;
- }
-
- public void removeUid(int uid) {
- mLastUidPolicyTimeMs.delete(uid);
- }
-
- public void removeUidsInRange(int startUid, int endUid) {
- mLastUidPolicyTimeMs.put(startUid, null);
- mLastUidPolicyTimeMs.put(endUid, null);
- final int firstIndex = mLastUidPolicyTimeMs.indexOfKey(startUid);
- final int lastIndex = mLastUidPolicyTimeMs.indexOfKey(endUid);
- mLastUidPolicyTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
- }
-}
diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
deleted file mode 100644
index 5b46d0f..0000000
--- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * Copyright (C) 2017 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.internal.os;
-
-import static com.android.internal.util.Preconditions.checkNotNull;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.StrictMode;
-import android.util.IntArray;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.IntBuffer;
-import java.util.function.Consumer;
-
-/**
- * Reads /proc/uid_time_in_state which has the format:
- *
- * uid: [freq1] [freq2] [freq3] ...
- * [uid1]: [time in freq1] [time in freq2] [time in freq3] ...
- * [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
- * ...
- *
- * Binary variation reads /proc/uid_cpupower/time_in_state in the following format:
- * [n, uid0, time0a, time0b, ..., time0n,
- * uid1, time1a, time1b, ..., time1n,
- * uid2, time2a, time2b, ..., time2n, etc.]
- * where n is the total number of frequencies.
- *
- * This provides the times a UID's processes spent executing at each different cpu frequency.
- * The file contains a monotonically increasing count of time for a single boot. This class
- * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
- * delta.
- *
- * This class uses a throttler to reject any {@link #readDelta} call within
- * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
- * which has a shorter throttle interval and returns cached result from last read when the request
- * is throttled.
- *
- * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
- * caller has its own view of delta.
- */
-public class KernelUidCpuFreqTimeReader extends
- KernelUidCpuTimeReaderBase<KernelUidCpuFreqTimeReader.Callback> {
- private static final String TAG = KernelUidCpuFreqTimeReader.class.getSimpleName();
- static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
-
- public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
- void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
- }
-
- private long[] mCpuFreqs;
- private long[] mCurTimes; // Reuse to prevent GC.
- private long[] mDeltaTimes; // Reuse to prevent GC.
- private int mCpuFreqsCount;
- private final KernelCpuProcReader mProcReader;
-
- private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
-
- // We check the existence of proc file a few times (just in case it is not ready yet when we
- // start reading) and if it is not available, we simply ignore further read requests.
- private static final int TOTAL_READ_ERROR_COUNT = 5;
- private int mReadErrorCounter;
- private boolean mPerClusterTimesAvailable;
- private boolean mAllUidTimesAvailable = true;
-
- public KernelUidCpuFreqTimeReader() {
- mProcReader = KernelCpuProcReader.getFreqTimeReaderInstance();
- }
-
- @VisibleForTesting
- public KernelUidCpuFreqTimeReader(KernelCpuProcReader procReader) {
- mProcReader = procReader;
- }
-
- public boolean perClusterTimesAvailable() {
- return mPerClusterTimesAvailable;
- }
-
- public boolean allUidTimesAvailable() {
- return mAllUidTimesAvailable;
- }
-
- public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
- return mLastUidCpuFreqTimeMs;
- }
-
- public long[] readFreqs(@NonNull PowerProfile powerProfile) {
- checkNotNull(powerProfile);
- if (mCpuFreqs != null) {
- // No need to read cpu freqs more than once.
- return mCpuFreqs;
- }
- if (!mAllUidTimesAvailable) {
- return null;
- }
- final int oldMask = StrictMode.allowThreadDiskReadsMask();
- try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
- return readFreqs(reader, powerProfile);
- } catch (IOException e) {
- if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
- mAllUidTimesAvailable = false;
- }
- Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
- return null;
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
- }
- }
-
- @VisibleForTesting
- public long[] readFreqs(BufferedReader reader, PowerProfile powerProfile)
- throws IOException {
- final String line = reader.readLine();
- if (line == null) {
- return null;
- }
- final String[] freqStr = line.split(" ");
- // First item would be "uid: " which needs to be ignored.
- mCpuFreqsCount = freqStr.length - 1;
- mCpuFreqs = new long[mCpuFreqsCount];
- mCurTimes = new long[mCpuFreqsCount];
- mDeltaTimes = new long[mCpuFreqsCount];
- for (int i = 0; i < mCpuFreqsCount; ++i) {
- mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
- }
-
- // Check if the freqs in the proc file correspond to per-cluster freqs.
- final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
- final int numClusters = powerProfile.getNumCpuClusters();
- if (numClusterFreqs.size() == numClusters) {
- mPerClusterTimesAvailable = true;
- for (int i = 0; i < numClusters; ++i) {
- if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) {
- mPerClusterTimesAvailable = false;
- break;
- }
- }
- } else {
- mPerClusterTimesAvailable = false;
- }
- Slog.i(TAG, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable);
- return mCpuFreqs;
- }
-
- @Override
- @VisibleForTesting
- public void readDeltaImpl(@Nullable Callback callback) {
- if (mCpuFreqs == null) {
- return;
- }
- readImpl((buf) -> {
- int uid = buf.get();
- long[] lastTimes = mLastUidCpuFreqTimeMs.get(uid);
- if (lastTimes == null) {
- lastTimes = new long[mCpuFreqsCount];
- mLastUidCpuFreqTimeMs.put(uid, lastTimes);
- }
- if (!getFreqTimeForUid(buf, mCurTimes)) {
- return;
- }
- boolean notify = false;
- boolean valid = true;
- for (int i = 0; i < mCpuFreqsCount; i++) {
- mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
- if (mDeltaTimes[i] < 0) {
- Slog.e(TAG, "Negative delta from freq time proc: " + mDeltaTimes[i]);
- valid = false;
- }
- notify |= mDeltaTimes[i] > 0;
- }
- if (notify && valid) {
- System.arraycopy(mCurTimes, 0, lastTimes, 0, mCpuFreqsCount);
- if (callback != null) {
- callback.onUidCpuFreqTime(uid, mDeltaTimes);
- }
- }
- });
- }
-
- public void readAbsolute(Callback callback) {
- readImpl((buf) -> {
- int uid = buf.get();
- if (getFreqTimeForUid(buf, mCurTimes)) {
- callback.onUidCpuFreqTime(uid, mCurTimes);
- }
- });
- }
-
- private boolean getFreqTimeForUid(IntBuffer buffer, long[] freqTime) {
- boolean valid = true;
- for (int i = 0; i < mCpuFreqsCount; i++) {
- freqTime[i] = (long) buffer.get() * 10; // Unit is 10ms.
- if (freqTime[i] < 0) {
- Slog.e(TAG, "Negative time from freq time proc: " + freqTime[i]);
- valid = false;
- }
- }
- return valid;
- }
-
- /**
- * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last
- * seen results while processing the buffer, while readAbsolute returns the absolute value read
- * from the buffer without storing. So readImpl contains the common logic of the two, leaving
- * the difference to a processUid function.
- *
- * @param processUid the callback function to process the uid entry in the buffer.
- */
- private void readImpl(Consumer<IntBuffer> processUid) {
- synchronized (mProcReader) {
- ByteBuffer bytes = mProcReader.readBytes();
- if (bytes == null || bytes.remaining() <= 4) {
- // Error already logged in mProcReader.
- return;
- }
- if ((bytes.remaining() & 3) != 0) {
- Slog.wtf(TAG, "Cannot parse freq time proc bytes to int: " + bytes.remaining());
- return;
- }
- IntBuffer buf = bytes.asIntBuffer();
- final int freqs = buf.get();
- if (freqs != mCpuFreqsCount) {
- Slog.wtf(TAG, "Cpu freqs expect " + mCpuFreqsCount + " , got " + freqs);
- return;
- }
- if (buf.remaining() % (freqs + 1) != 0) {
- Slog.wtf(TAG, "Freq time format error: " + buf.remaining() + " / " + (freqs + 1));
- return;
- }
- int numUids = buf.remaining() / (freqs + 1);
- for (int i = 0; i < numUids; i++) {
- processUid.accept(buf);
- }
- if (DEBUG) {
- Slog.d(TAG, "Read uids: #" + numUids);
- }
- }
- }
-
- public void removeUid(int uid) {
- mLastUidCpuFreqTimeMs.delete(uid);
- }
-
- public void removeUidsInRange(int startUid, int endUid) {
- mLastUidCpuFreqTimeMs.put(startUid, null);
- mLastUidCpuFreqTimeMs.put(endUid, null);
- final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid);
- final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid);
- mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
- }
-
- /**
- * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs
- * read from the proc file.
- *
- * We need to assume that freqs in each cluster are strictly increasing.
- * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means
- * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52)
- *
- * @return an IntArray filled with no. of freqs in each cluster.
- */
- private IntArray extractClusterInfoFromProcFileFreqs() {
- final IntArray numClusterFreqs = new IntArray();
- int freqsFound = 0;
- for (int i = 0; i < mCpuFreqsCount; ++i) {
- freqsFound++;
- if (i + 1 == mCpuFreqsCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) {
- numClusterFreqs.add(freqsFound);
- freqsFound = 0;
- }
- }
- return numClusterFreqs;
- }
-}
diff --git a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
deleted file mode 100644
index 97b7211..0000000
--- a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2015 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.internal.os;
-
-import android.annotation.Nullable;
-import android.os.StrictMode;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.Slog;
-import android.util.SparseLongArray;
-import android.util.TimeUtils;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-
-/**
- * Reads /proc/uid_cputime/show_uid_stat which has the line format:
- *
- * uid: user_time_micro_seconds system_time_micro_seconds power_in_milli-amp-micro_seconds
- *
- * This provides the time a UID's processes spent executing in user-space and kernel-space.
- * The file contains a monotonically increasing count of time for a single boot. This class
- * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
- * delta.
- */
-public class KernelUidCpuTimeReader extends
- KernelUidCpuTimeReaderBase<KernelUidCpuTimeReader.Callback> {
- private static final String TAG = KernelUidCpuTimeReader.class.getSimpleName();
- private static final String sProcFile = "/proc/uid_cputime/show_uid_stat";
- private static final String sRemoveUidProcFile = "/proc/uid_cputime/remove_uid_range";
-
- /**
- * Callback interface for processing each line of the proc file.
- */
- public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
- /**
- * @param uid UID of the app
- * @param userTimeUs time spent executing in user space in microseconds
- * @param systemTimeUs time spent executing in kernel space in microseconds
- */
- void onUidCpuTime(int uid, long userTimeUs, long systemTimeUs);
- }
-
- private SparseLongArray mLastUserTimeUs = new SparseLongArray();
- private SparseLongArray mLastSystemTimeUs = new SparseLongArray();
- private long mLastTimeReadUs = 0;
-
- /**
- * Reads the proc file, calling into the callback with a delta of time for each UID.
- *
- * @param callback The callback to invoke for each line of the proc file. If null,
- * the data is consumed and subsequent calls to readDelta will provide
- * a fresh delta.
- */
- @Override
- protected void readDeltaImpl(@Nullable Callback callback) {
- final int oldMask = StrictMode.allowThreadDiskReadsMask();
- long nowUs = SystemClock.elapsedRealtime() * 1000;
- try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
- TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
- String line;
- while ((line = reader.readLine()) != null) {
- splitter.setString(line);
- final String uidStr = splitter.next();
- final int uid = Integer.parseInt(uidStr.substring(0, uidStr.length() - 1), 10);
- final long userTimeUs = Long.parseLong(splitter.next(), 10);
- final long systemTimeUs = Long.parseLong(splitter.next(), 10);
-
- boolean notifyCallback = false;
- long userTimeDeltaUs = userTimeUs;
- long systemTimeDeltaUs = systemTimeUs;
- // Only report if there is a callback and if this is not the first read.
- if (callback != null && mLastTimeReadUs != 0) {
- int index = mLastUserTimeUs.indexOfKey(uid);
- if (index >= 0) {
- userTimeDeltaUs -= mLastUserTimeUs.valueAt(index);
- systemTimeDeltaUs -= mLastSystemTimeUs.valueAt(index);
-
- final long timeDiffUs = nowUs - mLastTimeReadUs;
- if (userTimeDeltaUs < 0 || systemTimeDeltaUs < 0) {
- StringBuilder sb = new StringBuilder("Malformed cpu data for UID=");
- sb.append(uid).append("!\n");
- sb.append("Time between reads: ");
- TimeUtils.formatDuration(timeDiffUs / 1000, sb);
- sb.append("\n");
- sb.append("Previous times: u=");
- TimeUtils.formatDuration(mLastUserTimeUs.valueAt(index) / 1000, sb);
- sb.append(" s=");
- TimeUtils.formatDuration(mLastSystemTimeUs.valueAt(index) / 1000, sb);
-
- sb.append("\nCurrent times: u=");
- TimeUtils.formatDuration(userTimeUs / 1000, sb);
- sb.append(" s=");
- TimeUtils.formatDuration(systemTimeUs / 1000, sb);
- sb.append("\nDelta: u=");
- TimeUtils.formatDuration(userTimeDeltaUs / 1000, sb);
- sb.append(" s=");
- TimeUtils.formatDuration(systemTimeDeltaUs / 1000, sb);
- Slog.e(TAG, sb.toString());
-
- userTimeDeltaUs = 0;
- systemTimeDeltaUs = 0;
- }
- }
-
- notifyCallback = (userTimeDeltaUs != 0 || systemTimeDeltaUs != 0);
- }
- mLastUserTimeUs.put(uid, userTimeUs);
- mLastSystemTimeUs.put(uid, systemTimeUs);
- if (notifyCallback) {
- callback.onUidCpuTime(uid, userTimeDeltaUs, systemTimeDeltaUs);
- }
- }
- } catch (IOException e) {
- Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage());
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
- }
- mLastTimeReadUs = nowUs;
- }
-
- /**
- * Reads the proc file, calling into the callback with raw absolute value of time for each UID.
- * @param callback The callback to invoke for each line of the proc file.
- */
- public void readAbsolute(Callback callback) {
- final int oldMask = StrictMode.allowThreadDiskReadsMask();
- try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
- TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
- String line;
- while ((line = reader.readLine()) != null) {
- splitter.setString(line);
- final String uidStr = splitter.next();
- final int uid = Integer.parseInt(uidStr.substring(0, uidStr.length() - 1), 10);
- final long userTimeUs = Long.parseLong(splitter.next(), 10);
- final long systemTimeUs = Long.parseLong(splitter.next(), 10);
- callback.onUidCpuTime(uid, userTimeUs, systemTimeUs);
- }
- } catch (IOException e) {
- Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage());
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
- }
- }
-
- /**
- * Removes the UID from the kernel module and from internal accounting data. Only
- * {@link BatteryStatsImpl} and its child processes should call this, as the change on Kernel is
- * visible system wide.
- *
- * @param uid The UID to remove.
- */
- public void removeUid(int uid) {
- final int index = mLastSystemTimeUs.indexOfKey(uid);
- if (index >= 0) {
- mLastSystemTimeUs.removeAt(index);
- mLastUserTimeUs.removeAt(index);
- }
- removeUidsFromKernelModule(uid, uid);
- }
-
- /**
- * Removes UIDs in a given range from the kernel module and internal accounting data. Only
- * {@link BatteryStatsImpl} and its child processes should call this, as the change on Kernel is
- * visible system wide.
- *
- * @param startUid the first uid to remove
- * @param endUid the last uid to remove
- */
- public void removeUidsInRange(int startUid, int endUid) {
- if (endUid < startUid) {
- return;
- }
- mLastSystemTimeUs.put(startUid, 0);
- mLastUserTimeUs.put(startUid, 0);
- mLastSystemTimeUs.put(endUid, 0);
- mLastUserTimeUs.put(endUid, 0);
- final int startIndex = mLastSystemTimeUs.indexOfKey(startUid);
- final int endIndex = mLastSystemTimeUs.indexOfKey(endUid);
- mLastSystemTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1);
- mLastUserTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1);
- removeUidsFromKernelModule(startUid, endUid);
- }
-
- private void removeUidsFromKernelModule(int startUid, int endUid) {
- Slog.d(TAG, "Removing uids " + startUid + "-" + endUid);
- final int oldMask = StrictMode.allowThreadDiskWritesMask();
- try (FileWriter writer = new FileWriter(sRemoveUidProcFile)) {
- writer.write(startUid + "-" + endUid);
- writer.flush();
- } catch (IOException e) {
- Slog.e(TAG, "failed to remove uids " + startUid + " - " + endUid
- + " from uid_cputime module", e);
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
- }
- }
-}
diff --git a/core/java/com/android/internal/os/KernelUidCpuTimeReaderBase.java b/core/java/com/android/internal/os/KernelUidCpuTimeReaderBase.java
deleted file mode 100644
index 11e50e1..0000000
--- a/core/java/com/android/internal/os/KernelUidCpuTimeReaderBase.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 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.internal.os;
-
-import android.annotation.Nullable;
-import android.os.SystemClock;
-import android.util.Slog;
-
-/**
- * The base class of all KernelUidCpuTimeReaders.
- *
- * This class is NOT designed to be thread-safe or accessed by more than one caller (due to
- * the nature of {@link #readDelta(Callback)}).
- */
-public abstract class KernelUidCpuTimeReaderBase<T extends KernelUidCpuTimeReaderBase.Callback> {
- protected static final boolean DEBUG = false;
- // Throttle interval in milliseconds
- private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L;
-
- private final String TAG = this.getClass().getSimpleName();
- private long mLastTimeReadMs = Long.MIN_VALUE;
- private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
-
- // A generic Callback interface (used by readDelta) to be extended by subclasses.
- public interface Callback {
- }
-
- public void readDelta(@Nullable T cb) {
- if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) {
- if (DEBUG) {
- Slog.d(TAG, "Throttle");
- }
- return;
- }
- readDeltaImpl(cb);
- mLastTimeReadMs = SystemClock.elapsedRealtime();
- }
-
- protected abstract void readDeltaImpl(@Nullable T cb);
-
- public void setThrottleInterval(long throttleInterval) {
- if (throttleInterval >= 0) {
- mThrottleInterval = throttleInterval;
- }
- }
-}
diff --git a/core/java/com/android/internal/os/LooperStats.java b/core/java/com/android/internal/os/LooperStats.java
index 9a7fb9f..0f0eedd 100644
--- a/core/java/com/android/internal/os/LooperStats.java
+++ b/core/java/com/android/internal/os/LooperStats.java
@@ -37,6 +37,7 @@
* @hide Only for use within the system server.
*/
public class LooperStats implements Looper.Observer {
+ public static final String DEBUG_ENTRY_PREFIX = "__DEBUG_";
private static final int SESSION_POOL_SIZE = 50;
@GuardedBy("mLock")
@@ -165,7 +166,7 @@
}
private ExportedEntry createDebugEntry(String variableName, long value) {
- final Entry entry = new Entry("__DEBUG_" + variableName);
+ final Entry entry = new Entry(DEBUG_ENTRY_PREFIX + variableName);
entry.messageCount = 1;
entry.recordedMessageCount = 1;
entry.totalLatencyMicro = value;
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 705bae4..069413f 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -16,13 +16,33 @@
package com.android.internal.os;
+import static android.system.OsConstants.O_CLOEXEC;
+
+import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC;
+
+import android.net.Credentials;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.os.FactoryTest;
import android.os.IVold;
+import android.os.Process;
+import android.os.SystemProperties;
import android.os.Trace;
import android.system.ErrnoException;
import android.system.Os;
+import android.util.Log;
import dalvik.system.ZygoteHooks;
+import libcore.io.IoUtils;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
/** @hide */
public final class Zygote {
/*
@@ -94,6 +114,24 @@
/** Read-write external storage should be mounted instead of package sandbox */
public static final int MOUNT_EXTERNAL_FULL = IVold.REMOUNT_MODE_FULL;
+ /** Number of bytes sent to the Zygote over blastula pipes or the pool event FD */
+ public static final int BLASTULA_MANAGEMENT_MESSAGE_BYTES = 8;
+
+ /**
+ * If the blastula pool should be created and used to start applications.
+ *
+ * Setting this value to false will disable the creation, maintenance, and use of the blastula
+ * pool. When the blastula pool is disabled the application lifecycle will be identical to
+ * previous versions of Android.
+ */
+ public static final boolean BLASTULA_POOL_ENABLED = false;
+
+ /**
+ * File descriptor used for communication between the signal handler and the ZygoteServer poll
+ * loop.
+ * */
+ protected static FileDescriptor sBlastulaPoolEventFD;
+
private static final ZygoteHooks VM_HOOKS = new ZygoteHooks();
/**
@@ -123,10 +161,52 @@
*/
public static final String CHILD_ZYGOTE_UID_RANGE_END = "--uid-range-end=";
+ /** Prefix prepended to socket names created by init */
+ private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
+
+ /**
+ * The maximum value that the sBlastulaPoolMax variable may take. This value
+ * is a mirror of BLASTULA_POOL_MAX_LIMIT found in com_android_internal_os_Zygote.cpp.
+ */
+ static final int BLASTULA_POOL_MAX_LIMIT = 10;
+
+ /**
+ * The minimum value that the sBlastulaPoolMin variable may take.
+ */
+ static final int BLASTULA_POOL_MIN_LIMIT = 1;
+
+ /**
+ * The runtime-adjustable maximum Blastula pool size.
+ */
+ static int sBlastulaPoolMax = BLASTULA_POOL_MAX_LIMIT;
+
+ /**
+ * The runtime-adjustable minimum Blastula pool size.
+ */
+ static int sBlastulaPoolMin = BLASTULA_POOL_MIN_LIMIT;
+
+ /**
+ * The runtime-adjustable value used to determine when to re-fill the
+ * blastula pool. The pool will be re-filled when
+ * (sBlastulaPoolMax - gBlastulaPoolCount) >= sBlastulaPoolRefillThreshold.
+ */
+ // TODO (chriswailes): This must be updated at the same time as sBlastulaPoolMax.
+ static int sBlastulaPoolRefillThreshold = (sBlastulaPoolMax / 2);
+
+ /**
+ * @hide for internal use only
+ */
+ public static final int SOCKET_BUFFER_SIZE = 256;
+
+ private static LocalServerSocket sBlastulaPoolSocket = null;
+
+ /** a prototype instance for a future List.toArray() */
+ protected static final int[][] INT_ARRAY_2D = new int[0][0];
+
private Zygote() {}
/** Called for some security initialization before any fork. */
- native static void nativeSecurityInit();
+ static native void nativeSecurityInit();
/**
* Forks a new VM instance. The current VM must have been started
@@ -165,14 +245,14 @@
public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
- String packageName, String[] packagesForUid, String[] visibleVolIds) {
+ String packageName, String[] packagesForUID, String[] visibleVolIDs) {
VM_HOOKS.preFork();
// Resets nice priority for zygote process.
resetNicePriority();
int pid = nativeForkAndSpecialize(
- uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
- fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName,
- packagesForUid, visibleVolIds);
+ uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
+ fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName,
+ packagesForUID, visibleVolIDs);
// Enable tracing as soon as possible for the child process.
if (pid == 0) {
Trace.setTracingEnabled(true, runtimeFlags);
@@ -184,15 +264,65 @@
return pid;
}
- native private static int nativeForkAndSpecialize(int uid, int gid, int[] gids,int runtimeFlags,
- int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
- int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
- String packageName, String[] packagesForUid, String[] visibleVolIds);
+ private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
+ int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
+ int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
+ String appDataDir, String packageName, String[] packagesForUID, String[] visibleVolIDs);
+
+ /**
+ * Specialize a Blastula instance. The current VM must have been started
+ * with the -Xzygote flag.
+ *
+ * @param uid The UNIX uid that the new process should setuid() to before spawning any threads
+ * @param gid The UNIX gid that the new process should setgid() to before spawning any threads
+ * @param gids null-ok; A list of UNIX gids that the new process should
+ * setgroups() to before spawning any threads
+ * @param runtimeFlags Bit flags that enable ART features
+ * @param rlimits null-ok An array of rlimit tuples, with the second
+ * dimension having a length of 3 and representing
+ * (resource, rlim_cur, rlim_max). These are set via the posix
+ * setrlimit(2) call.
+ * @param seInfo null-ok A string specifying SELinux information for
+ * the new process.
+ * @param niceName null-ok A string specifying the process name.
+ * @param startChildZygote If true, the new child process will itself be a
+ * new zygote process.
+ * @param instructionSet null-ok The instruction set to use.
+ * @param appDataDir null-ok The data directory of the app.
+ */
+ public static void specializeBlastula(int uid, int gid, int[] gids, int runtimeFlags,
+ int[][] rlimits, int mountExternal, String seInfo, String niceName,
+ boolean startChildZygote, String instructionSet, String appDataDir, String packageName,
+ String[] packagesForUID, String[] visibleVolIDs) {
+
+ nativeSpecializeBlastula(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
+ niceName, startChildZygote, instructionSet, appDataDir,
+ packageName, packagesForUID, visibleVolIDs);
+
+ // Enable tracing as soon as possible for the child process.
+ Trace.setTracingEnabled(true, runtimeFlags);
+
+ // Note that this event ends at the end of handleChildProc.
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
+
+ /*
+ * This is called here (instead of after the fork but before the specialize) to maintain
+ * consistancy with the code paths for forkAndSpecialize.
+ *
+ * TODO (chriswailes): Look into moving this to immediately after the fork.
+ */
+ VM_HOOKS.postForkCommon();
+ }
+
+ private static native void nativeSpecializeBlastula(int uid, int gid, int[] gids,
+ int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
+ boolean startChildZygote, String instructionSet, String appDataDir, String packageName,
+ String[] packagesForUID, String[] visibleVolIDs);
/**
* Called to do any initialization before starting an application.
*/
- native static void nativePreApplicationInit();
+ static native void nativePreApplicationInit();
/**
* Special method to start the system server process. In addition to the
@@ -223,7 +353,8 @@
// Resets nice priority for zygote process.
resetNicePriority();
int pid = nativeForkSystemServer(
- uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities, effectiveCapabilities);
+ uid, gid, gids, runtimeFlags, rlimits,
+ permittedCapabilities, effectiveCapabilities);
// Enable tracing as soon as we enter the system_server.
if (pid == 0) {
Trace.setTracingEnabled(true, runtimeFlags);
@@ -232,13 +363,13 @@
return pid;
}
- native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
+ private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
/**
* Lets children of the zygote inherit open file descriptors to this path.
*/
- native protected static void nativeAllowFileAcrossFork(String path);
+ protected static native void nativeAllowFileAcrossFork(String path);
/**
* Installs a seccomp filter that limits setresuid()/setresgid() to the passed-in range
@@ -251,7 +382,485 @@
* Zygote unmount storage space on initializing.
* This method is called once.
*/
- native protected static void nativeUnmountStorageOnInit();
+ protected static native void nativeUnmountStorageOnInit();
+
+ /**
+ * Get socket file descriptors (opened by init) from the environment and
+ * store them for access from native code later.
+ *
+ * @param isPrimary True if this is the zygote process, false if it is zygote_secondary
+ */
+ public static void getSocketFDs(boolean isPrimary) {
+ nativeGetSocketFDs(isPrimary);
+ }
+
+ protected static native void nativeGetSocketFDs(boolean isPrimary);
+
+ /**
+ * Initialize the blastula pool and fill it with the desired number of
+ * processes.
+ */
+ protected static Runnable initBlastulaPool() {
+ if (BLASTULA_POOL_ENABLED) {
+ sBlastulaPoolEventFD = getBlastulaPoolEventFD();
+
+ return fillBlastulaPool(null);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Checks to see if the current policy says that pool should be refilled, and spawns new
+ * blastulas if necessary.
+ *
+ * NOTE: This function doesn't need to be guarded with BLASTULA_POOL_ENABLED because it is
+ * only called from contexts that are only valid if the pool is enabled.
+ *
+ * @param sessionSocketRawFDs Anonymous session sockets that are currently open
+ * @return In the Zygote process this function will always return null; in blastula processes
+ * this function will return a Runnable object representing the new application that is
+ * passed up from blastulaMain.
+ */
+ protected static Runnable fillBlastulaPool(int[] sessionSocketRawFDs) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillBlastulaPool");
+
+ int blastulaPoolCount = getBlastulaPoolCount();
+
+ int numBlastulasToSpawn = sBlastulaPoolMax - blastulaPoolCount;
+
+ if (blastulaPoolCount < sBlastulaPoolMin
+ || numBlastulasToSpawn >= sBlastulaPoolRefillThreshold) {
+
+ // Disable some VM functionality and reset some system values
+ // before forking.
+ VM_HOOKS.preFork();
+ resetNicePriority();
+
+ while (blastulaPoolCount++ < sBlastulaPoolMax) {
+ Runnable caller = forkBlastula(sessionSocketRawFDs);
+
+ if (caller != null) {
+ return caller;
+ }
+ }
+
+ // Re-enable runtime services for the Zygote. Blastula services
+ // are re-enabled in specializeBlastula.
+ VM_HOOKS.postForkCommon();
+
+ Log.i("zygote", "Filled the blastula pool. New blastulas: " + numBlastulasToSpawn);
+ }
+
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ return null;
+ }
+
+ /**
+ * @return Number of blastulas currently in the pool
+ */
+ private static int getBlastulaPoolCount() {
+ return nativeGetBlastulaPoolCount();
+ }
+
+ private static native int nativeGetBlastulaPoolCount();
+
+ /**
+ * @return The event FD used for communication between the signal handler and the ZygoteServer
+ * poll loop
+ */
+ private static FileDescriptor getBlastulaPoolEventFD() {
+ FileDescriptor fd = new FileDescriptor();
+ fd.setInt$(nativeGetBlastulaPoolEventFD());
+
+ return fd;
+ }
+
+ private static native int nativeGetBlastulaPoolEventFD();
+
+ /**
+ * Fork a new blastula process from the zygote
+ *
+ * @param sessionSocketRawFDs Anonymous session sockets that are currently open
+ * @return In the Zygote process this function will always return null; in blastula processes
+ * this function will return a Runnable object representing the new application that is
+ * passed up from blastulaMain.
+ */
+ private static Runnable forkBlastula(int[] sessionSocketRawFDs) {
+ FileDescriptor[] pipeFDs = null;
+
+ try {
+ pipeFDs = Os.pipe2(O_CLOEXEC);
+ } catch (ErrnoException errnoEx) {
+ throw new IllegalStateException("Unable to create blastula pipe.", errnoEx);
+ }
+
+ int pid =
+ nativeForkBlastula(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(), sessionSocketRawFDs);
+
+ if (pid == 0) {
+ IoUtils.closeQuietly(pipeFDs[0]);
+ return blastulaMain(pipeFDs[1]);
+ } else {
+ // The read-end of the pipe will be closed by the native code.
+ // See removeBlastulaTableEntry();
+ IoUtils.closeQuietly(pipeFDs[1]);
+ return null;
+ }
+ }
+
+ private static native int nativeForkBlastula(int readPipeFD,
+ int writePipeFD,
+ int[] sessionSocketRawFDs);
+
+ /**
+ * This function is used by blastulas to wait for specialization requests from the system
+ * server.
+ *
+ * @param writePipe The write end of the reporting pipe used to communicate with the poll loop
+ * of the ZygoteServer.
+ * @return A runnable oject representing the new application.
+ */
+ static Runnable blastulaMain(FileDescriptor writePipe) {
+ final int pid = Process.myPid();
+
+ LocalSocket sessionSocket = null;
+ DataOutputStream blastulaOutputStream = null;
+ Credentials peerCredentials = null;
+ String[] argStrings = null;
+
+ while (true) {
+ try {
+ sessionSocket = sBlastulaPoolSocket.accept();
+
+ BufferedReader blastulaReader =
+ new BufferedReader(new InputStreamReader(sessionSocket.getInputStream()));
+ blastulaOutputStream =
+ new DataOutputStream(sessionSocket.getOutputStream());
+
+ peerCredentials = sessionSocket.getPeerCredentials();
+
+ argStrings = readArgumentList(blastulaReader);
+
+ if (argStrings != null) {
+ break;
+ } else {
+ Log.e("Blastula", "Truncated command received.");
+ IoUtils.closeQuietly(sessionSocket);
+ }
+ } catch (IOException ioEx) {
+ Log.e("Blastula", "Failed to read command: " + ioEx.getMessage());
+ IoUtils.closeQuietly(sessionSocket);
+ }
+ }
+
+ ZygoteArguments args = new ZygoteArguments(argStrings);
+
+ // TODO (chriswailes): Should this only be run for debug builds?
+ validateBlastulaCommand(args);
+
+ applyUidSecurityPolicy(args, peerCredentials);
+ applyDebuggerSystemProperty(args);
+
+ int[][] rlimits = null;
+
+ if (args.mRLimits != null) {
+ rlimits = args.mRLimits.toArray(INT_ARRAY_2D);
+ }
+
+ // This must happen before the SELinux policy for this process is
+ // changed when specializing.
+ try {
+ // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a
+ // Process.ProcessStartResult object.
+ blastulaOutputStream.writeInt(pid);
+ } catch (IOException ioEx) {
+ Log.e("Blastula", "Failed to write response to session socket: " + ioEx.getMessage());
+ System.exit(-1);
+ } finally {
+ IoUtils.closeQuietly(sessionSocket);
+ IoUtils.closeQuietly(sBlastulaPoolSocket);
+ }
+
+ try {
+ ByteArrayOutputStream buffer =
+ new ByteArrayOutputStream(Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES);
+ DataOutputStream outputStream = new DataOutputStream(buffer);
+
+ // This is written as a long so that the blastula reporting pipe and blastula pool
+ // event FD handlers in ZygoteServer.runSelectLoop can be unified. These two cases
+ // should both send/receive 8 bytes.
+ outputStream.writeLong(pid);
+ outputStream.flush();
+
+ Os.write(writePipe, buffer.toByteArray(), 0, buffer.size());
+ } catch (Exception ex) {
+ Log.e("Blastula",
+ String.format("Failed to write PID (%d) to pipe (%d): %s",
+ pid, writePipe.getInt$(), ex.getMessage()));
+ System.exit(-1);
+ } finally {
+ IoUtils.closeQuietly(writePipe);
+ }
+
+ specializeBlastula(args.mUid, args.mGid, args.mGids,
+ args.mRuntimeFlags, rlimits, args.mMountExternal,
+ args.mSeInfo, args.mNiceName, args.mStartChildZygote,
+ args.mInstructionSet, args.mAppDataDir, args.mPackageName,
+ args.mPackagesForUid, args.mVisibleVolIds);
+
+ if (args.mNiceName != null) {
+ Process.setArgV0(args.mNiceName);
+ }
+
+ // End of the postFork event.
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ return ZygoteInit.zygoteInit(args.mTargetSdkVersion,
+ args.mRemainingArgs,
+ null /* classLoader */);
+ }
+
+ private static final String BLASTULA_ERROR_PREFIX = "Invalid command to blastula: ";
+
+ /**
+ * Checks a set of zygote arguments to see if they can be handled by a blastula. Throws an
+ * exception if an invalid arugment is encountered.
+ * @param args The arguments to test
+ */
+ static void validateBlastulaCommand(ZygoteArguments args) {
+ if (args.mAbiListQuery) {
+ throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--query-abi-list");
+ } else if (args.mPidQuery) {
+ throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--get-pid");
+ } else if (args.mPreloadDefault) {
+ throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-default");
+ } else if (args.mPreloadPackage != null) {
+ throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-package");
+ } else if (args.mPreloadApp != null) {
+ throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-app");
+ } else if (args.mStartChildZygote) {
+ throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--start-child-zygote");
+ } else if (args.mApiBlacklistExemptions != null) {
+ throw new IllegalArgumentException(
+ BLASTULA_ERROR_PREFIX + "--set-api-blacklist-exemptions");
+ } else if (args.mHiddenApiAccessLogSampleRate != -1) {
+ throw new IllegalArgumentException(
+ BLASTULA_ERROR_PREFIX + "--hidden-api-log-sampling-rate=");
+ } else if (args.mInvokeWith != null) {
+ throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--invoke-with");
+ } else if (args.mPermittedCapabilities != 0 || args.mEffectiveCapabilities != 0) {
+ throw new ZygoteSecurityException("Client may not specify capabilities: "
+ + "permitted=0x" + Long.toHexString(args.mPermittedCapabilities)
+ + ", effective=0x" + Long.toHexString(args.mEffectiveCapabilities));
+ }
+ }
+
+ /**
+ * @return Raw file descriptors for the read-end of blastula reporting pipes.
+ */
+ protected static int[] getBlastulaPipeFDs() {
+ return nativeGetBlastulaPipeFDs();
+ }
+
+ private static native int[] nativeGetBlastulaPipeFDs();
+
+ /**
+ * Remove the blastula table entry for the provided process ID.
+ *
+ * @param blastulaPID Process ID of the entry to remove
+ * @return True if the entry was removed; false if it doesn't exist
+ */
+ protected static boolean removeBlastulaTableEntry(int blastulaPID) {
+ return nativeRemoveBlastulaTableEntry(blastulaPID);
+ }
+
+ private static native boolean nativeRemoveBlastulaTableEntry(int blastulaPID);
+
+ /**
+ * uid 1000 (Process.SYSTEM_UID) may specify any uid > 1000 in normal
+ * operation. It may also specify any gid and setgroups() list it chooses.
+ * In factory test mode, it may specify any UID.
+ *
+ * @param args non-null; zygote spawner arguments
+ * @param peer non-null; peer credentials
+ * @throws ZygoteSecurityException
+ */
+ protected static void applyUidSecurityPolicy(ZygoteArguments args, Credentials peer)
+ throws ZygoteSecurityException {
+
+ if (peer.getUid() == Process.SYSTEM_UID) {
+ /* In normal operation, SYSTEM_UID can only specify a restricted
+ * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
+ */
+ boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF;
+
+ if (uidRestricted && args.mUidSpecified && (args.mUid < Process.SYSTEM_UID)) {
+ throw new ZygoteSecurityException(
+ "System UID may not launch process with UID < "
+ + Process.SYSTEM_UID);
+ }
+ }
+
+ // If not otherwise specified, uid and gid are inherited from peer
+ if (!args.mUidSpecified) {
+ args.mUid = peer.getUid();
+ args.mUidSpecified = true;
+ }
+ if (!args.mGidSpecified) {
+ args.mGid = peer.getGid();
+ args.mGidSpecified = true;
+ }
+ }
+
+ /**
+ * Applies debugger system properties to the zygote arguments.
+ *
+ * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
+ * the debugger state is specified via the "--enable-jdwp" flag
+ * in the spawn request.
+ *
+ * @param args non-null; zygote spawner args
+ */
+ protected static void applyDebuggerSystemProperty(ZygoteArguments args) {
+ if (RoSystemProperties.DEBUGGABLE) {
+ args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
+ }
+ }
+
+ /**
+ * Applies zygote security policy.
+ * Based on the credentials of the process issuing a zygote command:
+ * <ol>
+ * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a
+ * wrapper command.
+ * <li> Any other uid may not specify any invoke-with argument.
+ * </ul>
+ *
+ * @param args non-null; zygote spawner arguments
+ * @param peer non-null; peer credentials
+ * @throws ZygoteSecurityException
+ */
+ protected static void applyInvokeWithSecurityPolicy(ZygoteArguments args, Credentials peer)
+ throws ZygoteSecurityException {
+ int peerUid = peer.getUid();
+
+ if (args.mInvokeWith != null && peerUid != 0
+ && (args.mRuntimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) {
+ throw new ZygoteSecurityException("Peer is permitted to specify an"
+ + "explicit invoke-with wrapper command only for debuggable"
+ + "applications.");
+ }
+ }
+
+ /**
+ * Applies invoke-with system properties to the zygote arguments.
+ *
+ * @param args non-null; zygote args
+ */
+ protected static void applyInvokeWithSystemProperty(ZygoteArguments args) {
+ if (args.mInvokeWith == null && args.mNiceName != null) {
+ String property = "wrap." + args.mNiceName;
+ args.mInvokeWith = SystemProperties.get(property);
+ if (args.mInvokeWith != null && args.mInvokeWith.length() == 0) {
+ args.mInvokeWith = null;
+ }
+ }
+ }
+
+ /**
+ * Reads an argument list from the provided socket
+ * @return Argument list or null if EOF is reached
+ * @throws IOException passed straight through
+ */
+ static String[] readArgumentList(BufferedReader socketReader) throws IOException {
+
+ /**
+ * See android.os.Process.zygoteSendArgsAndGetPid()
+ * Presently the wire format to the zygote process is:
+ * a) a count of arguments (argc, in essence)
+ * b) a number of newline-separated argument strings equal to count
+ *
+ * After the zygote process reads these it will write the pid of
+ * the child or -1 on failure.
+ */
+
+ int argc;
+
+ try {
+ String argc_string = socketReader.readLine();
+
+ if (argc_string == null) {
+ // EOF reached.
+ return null;
+ }
+ argc = Integer.parseInt(argc_string);
+
+ } catch (NumberFormatException ex) {
+ Log.e("Zygote", "Invalid Zygote wire format: non-int at argc");
+ throw new IOException("Invalid wire format");
+ }
+
+ // See bug 1092107: large argc can be used for a DOS attack
+ if (argc > MAX_ZYGOTE_ARGC) {
+ throw new IOException("Max arg count exceeded");
+ }
+
+ String[] args = new String[argc];
+ for (int arg_index = 0; arg_index < argc; arg_index++) {
+ args[arg_index] = socketReader.readLine();
+ if (args[arg_index] == null) {
+ // We got an unexpected EOF.
+ throw new IOException("Truncated request");
+ }
+ }
+
+ return args;
+ }
+
+ /**
+ * Creates a managed object representing the Blastula pool socket that has
+ * already been initialized and bound by init.
+ *
+ * TODO (chriswailes): Move the name selection logic into this function.
+ *
+ * @throws RuntimeException when open fails
+ */
+ static void createBlastulaSocket(String socketName) {
+ if (BLASTULA_POOL_ENABLED && sBlastulaPoolSocket == null) {
+ sBlastulaPoolSocket = createManagedSocketFromInitSocket(socketName);
+ }
+ }
+
+ /**
+ * Creates a managed LocalServerSocket object using a file descriptor
+ * created by an init.rc script. The init scripts that specify the
+ * sockets name can be found in system/core/rootdir. The socket is bound
+ * to the file system in the /dev/sockets/ directory, and the file
+ * descriptor is shared via the ANDROID_SOCKET_<socketName> environment
+ * variable.
+ */
+ static LocalServerSocket createManagedSocketFromInitSocket(String socketName) {
+ int fileDesc;
+ final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
+
+ try {
+ String env = System.getenv(fullSocketName);
+ fileDesc = Integer.parseInt(env);
+ } catch (RuntimeException ex) {
+ throw new RuntimeException("Socket unset or invalid: " + fullSocketName, ex);
+ }
+
+ try {
+ FileDescriptor fd = new FileDescriptor();
+ fd.setInt$(fileDesc);
+ return new LocalServerSocket(fd);
+ } catch (IOException ex) {
+ throw new RuntimeException(
+ "Error building socket from file descriptor: " + fileDesc, ex);
+ }
+ }
private static void callPostForkSystemServerHooks() {
// SystemServer specific post fork hooks run before child post fork hooks.
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
new file mode 100644
index 0000000..df89b26
--- /dev/null
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2007 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.internal.os;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Handles argument parsing for args related to the zygote spawner.
+ *
+ * Current recognized args:
+ * <ul>
+ * <li> --setuid=<i>uid of child process, defaults to 0</i>
+ * <li> --setgid=<i>gid of child process, defaults to 0</i>
+ * <li> --setgroups=<i>comma-separated list of supplimentary gid's</i>
+ * <li> --capabilities=<i>a pair of comma-separated integer strings
+ * indicating Linux capabilities(2) set for child. The first string
+ * represents the <code>permitted</code> set, and the second the
+ * <code>effective</code> set. Precede each with 0 or
+ * 0x for octal or hexidecimal value. If unspecified, both default to 0.
+ * This parameter is only applied if the uid of the new process will
+ * be non-0. </i>
+ * <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call.
+ * <code>r</code> is the resource, <code>c</code> and <code>m</code>
+ * are the settings for current and max value.</i>
+ * <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate.
+ * <li> --nice-name=<i>nice name to appear in ps</i>
+ * <li> --package-name=<i>package name this process belongs to</i>
+ * <li> --runtime-args indicates that the remaining arg list should
+ * be handed off to com.android.internal.os.RuntimeInit, rather than
+ * processed directly.
+ * Android runtime startup (eg, Binder initialization) is also eschewed.
+ * <li> [--] <args for RuntimeInit >
+ * </ul>
+ */
+class ZygoteArguments {
+
+ /**
+ * from --setuid
+ */
+ int mUid = 0;
+ boolean mUidSpecified;
+
+ /**
+ * from --setgid
+ */
+ int mGid = 0;
+ boolean mGidSpecified;
+
+ /**
+ * from --setgroups
+ */
+ int[] mGids;
+
+ /**
+ * From --runtime-flags.
+ */
+ int mRuntimeFlags;
+
+ /**
+ * From --mount-external
+ */
+ int mMountExternal = Zygote.MOUNT_EXTERNAL_NONE;
+
+ /**
+ * from --target-sdk-version.
+ */
+ int mTargetSdkVersion;
+ boolean mTargetSdkVersionSpecified;
+
+ /**
+ * from --nice-name
+ */
+ String mNiceName;
+
+ /**
+ * from --capabilities
+ */
+ boolean mCapabilitiesSpecified;
+ long mPermittedCapabilities;
+ long mEffectiveCapabilities;
+
+ /**
+ * from --seinfo
+ */
+ boolean mSeInfoSpecified;
+ String mSeInfo;
+
+ /**
+ * from all --rlimit=r,c,m
+ */
+ ArrayList<int[]> mRLimits;
+
+ /**
+ * from --invoke-with
+ */
+ String mInvokeWith;
+
+ /** from --package-name */
+ String mPackageName;
+
+ /** from --packages-for-uid */
+ String[] mPackagesForUid;
+
+ /** from --visible-vols */
+ String[] mVisibleVolIds;
+
+ /**
+ * Any args after and including the first non-option arg (or after a '--')
+ */
+ String[] mRemainingArgs;
+
+ /**
+ * Whether the current arguments constitute an ABI list query.
+ */
+ boolean mAbiListQuery;
+
+ /**
+ * The instruction set to use, or null when not important.
+ */
+ String mInstructionSet;
+
+ /**
+ * The app data directory. May be null, e.g., for the system server. Note that this might not be
+ * reliable in the case of process-sharing apps.
+ */
+ String mAppDataDir;
+
+ /**
+ * The APK path of the package to preload, when using --preload-package.
+ */
+ String mPreloadPackage;
+
+ /**
+ * A Base64 string representing a serialize ApplicationInfo Parcel,
+ when using --preload-app.
+ */
+ String mPreloadApp;
+
+ /**
+ * The native library path of the package to preload, when using --preload-package.
+ */
+ String mPreloadPackageLibs;
+
+ /**
+ * The filename of the native library to preload, when using --preload-package.
+ */
+ String mPreloadPackageLibFileName;
+
+ /**
+ * The cache key under which to enter the preloaded package into the classloader cache, when
+ * using --preload-package.
+ */
+ String mPreloadPackageCacheKey;
+
+ /**
+ * Whether this is a request to start preloading the default resources and classes. This
+ * argument only makes sense when the zygote is in lazy preload mode (i.e, when it's started
+ * with --enable-lazy-preload).
+ */
+ boolean mPreloadDefault;
+
+ /**
+ * Whether this is a request to start a zygote process as a child of this zygote. Set with
+ * --start-child-zygote. The remaining arguments must include the CHILD_ZYGOTE_SOCKET_NAME_ARG
+ * flag to indicate the abstract socket name that should be used for communication.
+ */
+ boolean mStartChildZygote;
+
+ /**
+ * Whether the current arguments constitute a request for the zygote's PID.
+ */
+ boolean mPidQuery;
+
+ /**
+ * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time, or
+ * when they change, via --set-api-blacklist-exemptions.
+ */
+ String[] mApiBlacklistExemptions;
+
+ /**
+ * Sampling rate for logging hidden API accesses to the event log. This is sent to the
+ * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate.
+ */
+ int mHiddenApiAccessLogSampleRate = -1;
+
+ /**
+ * Constructs instance and parses args
+ *
+ * @param args zygote command-line args
+ */
+ ZygoteArguments(String[] args) throws IllegalArgumentException {
+ parseArgs(args);
+ }
+
+ /**
+ * Parses the commandline arguments intended for the Zygote spawner (such as "--setuid=" and
+ * "--setgid=") and creates an array containing the remaining args.
+ *
+ * Per security review bug #1112214, duplicate args are disallowed in critical cases to make
+ * injection harder.
+ */
+ private void parseArgs(String[] args)
+ throws IllegalArgumentException {
+ int curArg = 0;
+
+ boolean seenRuntimeArgs = false;
+
+ boolean expectRuntimeArgs = true;
+ for ( /* curArg */ ; curArg < args.length; curArg++) {
+ String arg = args[curArg];
+
+ if (arg.equals("--")) {
+ curArg++;
+ break;
+ } else if (arg.startsWith("--setuid=")) {
+ if (mUidSpecified) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ mUidSpecified = true;
+ mUid = Integer.parseInt(
+ arg.substring(arg.indexOf('=') + 1));
+ } else if (arg.startsWith("--setgid=")) {
+ if (mGidSpecified) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ mGidSpecified = true;
+ mGid = Integer.parseInt(
+ arg.substring(arg.indexOf('=') + 1));
+ } else if (arg.startsWith("--target-sdk-version=")) {
+ if (mTargetSdkVersionSpecified) {
+ throw new IllegalArgumentException(
+ "Duplicate target-sdk-version specified");
+ }
+ mTargetSdkVersionSpecified = true;
+ mTargetSdkVersion = Integer.parseInt(
+ arg.substring(arg.indexOf('=') + 1));
+ } else if (arg.equals("--runtime-args")) {
+ seenRuntimeArgs = true;
+ } else if (arg.startsWith("--runtime-flags=")) {
+ mRuntimeFlags = Integer.parseInt(
+ arg.substring(arg.indexOf('=') + 1));
+ } else if (arg.startsWith("--seinfo=")) {
+ if (mSeInfoSpecified) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ mSeInfoSpecified = true;
+ mSeInfo = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.startsWith("--capabilities=")) {
+ if (mCapabilitiesSpecified) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ mCapabilitiesSpecified = true;
+ String capString = arg.substring(arg.indexOf('=') + 1);
+
+ String[] capStrings = capString.split(",", 2);
+
+ if (capStrings.length == 1) {
+ mEffectiveCapabilities = Long.decode(capStrings[0]);
+ mPermittedCapabilities = mEffectiveCapabilities;
+ } else {
+ mPermittedCapabilities = Long.decode(capStrings[0]);
+ mEffectiveCapabilities = Long.decode(capStrings[1]);
+ }
+ } else if (arg.startsWith("--rlimit=")) {
+ // Duplicate --rlimit arguments are specifically allowed.
+ String[] limitStrings = arg.substring(arg.indexOf('=') + 1).split(",");
+
+ if (limitStrings.length != 3) {
+ throw new IllegalArgumentException(
+ "--rlimit= should have 3 comma-delimited ints");
+ }
+ int[] rlimitTuple = new int[limitStrings.length];
+
+ for (int i = 0; i < limitStrings.length; i++) {
+ rlimitTuple[i] = Integer.parseInt(limitStrings[i]);
+ }
+
+ if (mRLimits == null) {
+ mRLimits = new ArrayList();
+ }
+
+ mRLimits.add(rlimitTuple);
+ } else if (arg.startsWith("--setgroups=")) {
+ if (mGids != null) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+
+ String[] params = arg.substring(arg.indexOf('=') + 1).split(",");
+
+ mGids = new int[params.length];
+
+ for (int i = params.length - 1; i >= 0; i--) {
+ mGids[i] = Integer.parseInt(params[i]);
+ }
+ } else if (arg.equals("--invoke-with")) {
+ if (mInvokeWith != null) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ try {
+ mInvokeWith = args[++curArg];
+ } catch (IndexOutOfBoundsException ex) {
+ throw new IllegalArgumentException(
+ "--invoke-with requires argument");
+ }
+ } else if (arg.startsWith("--nice-name=")) {
+ if (mNiceName != null) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ mNiceName = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.equals("--mount-external-default")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
+ } else if (arg.equals("--mount-external-read")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_READ;
+ } else if (arg.equals("--mount-external-write")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_WRITE;
+ } else if (arg.equals("--mount-external-full")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_FULL;
+ } else if (arg.equals("--mount-external-installer")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER;
+ } else if (arg.equals("--query-abi-list")) {
+ mAbiListQuery = true;
+ } else if (arg.equals("--get-pid")) {
+ mPidQuery = true;
+ } else if (arg.startsWith("--instruction-set=")) {
+ mInstructionSet = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.startsWith("--app-data-dir=")) {
+ mAppDataDir = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.equals("--preload-app")) {
+ mPreloadApp = args[++curArg];
+ } else if (arg.equals("--preload-package")) {
+ mPreloadPackage = args[++curArg];
+ mPreloadPackageLibs = args[++curArg];
+ mPreloadPackageLibFileName = args[++curArg];
+ mPreloadPackageCacheKey = args[++curArg];
+ } else if (arg.equals("--preload-default")) {
+ mPreloadDefault = true;
+ expectRuntimeArgs = false;
+ } else if (arg.equals("--start-child-zygote")) {
+ mStartChildZygote = true;
+ } else if (arg.equals("--set-api-blacklist-exemptions")) {
+ // consume all remaining args; this is a stand-alone command, never included
+ // with the regular fork command.
+ mApiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length);
+ curArg = args.length;
+ expectRuntimeArgs = false;
+ } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) {
+ String rateStr = arg.substring(arg.indexOf('=') + 1);
+ try {
+ mHiddenApiAccessLogSampleRate = Integer.parseInt(rateStr);
+ } catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException(
+ "Invalid log sampling rate: " + rateStr, nfe);
+ }
+ expectRuntimeArgs = false;
+ } else if (arg.startsWith("--package-name=")) {
+ if (mPackageName != null) {
+ throw new IllegalArgumentException("Duplicate arg specified");
+ }
+ mPackageName = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.startsWith("--packages-for-uid=")) {
+ mPackagesForUid = arg.substring(arg.indexOf('=') + 1).split(",");
+ } else if (arg.startsWith("--visible-vols=")) {
+ mVisibleVolIds = arg.substring(arg.indexOf('=') + 1).split(",");
+ } else {
+ break;
+ }
+ }
+
+ if (mAbiListQuery || mPidQuery) {
+ if (args.length - curArg > 0) {
+ throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
+ }
+ } else if (mPreloadPackage != null) {
+ if (args.length - curArg > 0) {
+ throw new IllegalArgumentException(
+ "Unexpected arguments after --preload-package.");
+ }
+ } else if (mPreloadApp != null) {
+ if (args.length - curArg > 0) {
+ throw new IllegalArgumentException(
+ "Unexpected arguments after --preload-app.");
+ }
+ } else if (expectRuntimeArgs) {
+ if (!seenRuntimeArgs) {
+ throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
+ }
+
+ mRemainingArgs = new String[args.length - curArg];
+ System.arraycopy(args, curArg, mRemainingArgs, 0, mRemainingArgs.length);
+ }
+
+ if (mStartChildZygote) {
+ boolean seenChildSocketArg = false;
+ for (String arg : mRemainingArgs) {
+ if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
+ seenChildSocketArg = true;
+ break;
+ }
+ }
+ if (!seenChildSocketArg) {
+ throw new IllegalArgumentException("--start-child-zygote specified "
+ + "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG);
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index ced798c..ffbe8eb 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -24,16 +24,13 @@
import static android.system.OsConstants.STDOUT_FILENO;
import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEOUT_MILLIS;
-import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC;
import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
import android.content.pm.ApplicationInfo;
import android.net.Credentials;
import android.net.LocalSocket;
-import android.os.FactoryTest;
import android.os.Parcel;
import android.os.Process;
-import android.os.SystemProperties;
import android.os.Trace;
import android.system.ErrnoException;
import android.system.Os;
@@ -52,8 +49,6 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Base64;
/**
@@ -62,9 +57,6 @@
class ZygoteConnection {
private static final String TAG = "Zygote";
- /** a prototype instance for a future List.toArray() */
- private static final int[][] intArray2d = new int[0][0];
-
/**
* The command socket.
*
@@ -113,7 +105,7 @@
*
* @return null-ok; file descriptor
*/
- FileDescriptor getFileDesciptor() {
+ FileDescriptor getFileDescriptor() {
return mSocket.getFileDescriptor();
}
@@ -127,11 +119,13 @@
*/
Runnable processOneCommand(ZygoteServer zygoteServer) {
String args[];
- Arguments parsedArgs = null;
+ ZygoteArguments parsedArgs = null;
FileDescriptor[] descriptors;
try {
- args = readArgumentList();
+ args = Zygote.readArgumentList(mSocketReader);
+
+ // TODO (chriswailes): Remove this and add an assert.
descriptors = mSocket.getAncillaryFileDescriptors();
} catch (IOException ex) {
throw new IllegalStateException("IOException on command socket", ex);
@@ -148,26 +142,26 @@
FileDescriptor childPipeFd = null;
FileDescriptor serverPipeFd = null;
- parsedArgs = new Arguments(args);
+ parsedArgs = new ZygoteArguments(args);
- if (parsedArgs.abiListQuery) {
+ if (parsedArgs.mAbiListQuery) {
handleAbiListQuery();
return null;
}
- if (parsedArgs.pidQuery) {
+ if (parsedArgs.mPidQuery) {
handlePidQuery();
return null;
}
- if (parsedArgs.preloadDefault) {
+ if (parsedArgs.mPreloadDefault) {
handlePreload();
return null;
}
- if (parsedArgs.preloadPackage != null) {
- handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs,
- parsedArgs.preloadPackageLibFileName, parsedArgs.preloadPackageCacheKey);
+ if (parsedArgs.mPreloadPackage != null) {
+ handlePreloadPackage(parsedArgs.mPreloadPackage, parsedArgs.mPreloadPackageLibs,
+ parsedArgs.mPreloadPackageLibFileName, parsedArgs.mPreloadPackageCacheKey);
return null;
}
@@ -186,37 +180,37 @@
return null;
}
- if (parsedArgs.apiBlacklistExemptions != null) {
- handleApiBlacklistExemptions(parsedArgs.apiBlacklistExemptions);
+ if (parsedArgs.mApiBlacklistExemptions != null) {
+ handleApiBlacklistExemptions(parsedArgs.mApiBlacklistExemptions);
return null;
}
- if (parsedArgs.hiddenApiAccessLogSampleRate != -1) {
- handleHiddenApiAccessLogSampleRate(parsedArgs.hiddenApiAccessLogSampleRate);
+ if (parsedArgs.mHiddenApiAccessLogSampleRate != -1) {
+ handleHiddenApiAccessLogSampleRate(parsedArgs.mHiddenApiAccessLogSampleRate);
return null;
}
- if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
- throw new ZygoteSecurityException("Client may not specify capabilities: " +
- "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
- ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));
+ if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) {
+ throw new ZygoteSecurityException("Client may not specify capabilities: "
+ + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities)
+ + ", effective=0x" + Long.toHexString(parsedArgs.mEffectiveCapabilities));
}
- applyUidSecurityPolicy(parsedArgs, peer);
- applyInvokeWithSecurityPolicy(parsedArgs, peer);
+ Zygote.applyUidSecurityPolicy(parsedArgs, peer);
+ Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer);
- applyDebuggerSystemProperty(parsedArgs);
- applyInvokeWithSystemProperty(parsedArgs);
+ Zygote.applyDebuggerSystemProperty(parsedArgs);
+ Zygote.applyInvokeWithSystemProperty(parsedArgs);
int[][] rlimits = null;
- if (parsedArgs.rlimits != null) {
- rlimits = parsedArgs.rlimits.toArray(intArray2d);
+ if (parsedArgs.mRLimits != null) {
+ rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D);
}
int[] fdsToIgnore = null;
- if (parsedArgs.invokeWith != null) {
+ if (parsedArgs.mInvokeWith != null) {
try {
FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
childPipeFd = pipeFds[1];
@@ -248,7 +242,7 @@
fdsToClose[0] = fd.getInt$();
}
- fd = zygoteServer.getServerSocketFileDescriptor();
+ fd = zygoteServer.getZygoteSocketFileDescriptor();
if (fd != null) {
fdsToClose[1] = fd.getInt$();
@@ -256,11 +250,11 @@
fd = null;
- pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
- parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
- parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote,
- parsedArgs.instructionSet, parsedArgs.appDataDir, parsedArgs.packageName,
- parsedArgs.packagesForUid, parsedArgs.visibleVolIds);
+ pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids,
+ parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
+ parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
+ parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mPackageName,
+ parsedArgs.mPackagesForUid, parsedArgs.mVisibleVolIds);
try {
if (pid == 0) {
@@ -272,7 +266,7 @@
serverPipeFd = null;
return handleChildProc(parsedArgs, descriptors, childPipeFd,
- parsedArgs.startChildZygote);
+ parsedArgs.mStartChildZygote);
} else {
// In the parent. A pid < 0 indicates a failure and will be handled in
// handleParentProc.
@@ -387,541 +381,6 @@
}
/**
- * Handles argument parsing for args related to the zygote spawner.
- *
- * Current recognized args:
- * <ul>
- * <li> --setuid=<i>uid of child process, defaults to 0</i>
- * <li> --setgid=<i>gid of child process, defaults to 0</i>
- * <li> --setgroups=<i>comma-separated list of supplimentary gid's</i>
- * <li> --capabilities=<i>a pair of comma-separated integer strings
- * indicating Linux capabilities(2) set for child. The first string
- * represents the <code>permitted</code> set, and the second the
- * <code>effective</code> set. Precede each with 0 or
- * 0x for octal or hexidecimal value. If unspecified, both default to 0.
- * This parameter is only applied if the uid of the new process will
- * be non-0. </i>
- * <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call.
- * <code>r</code> is the resource, <code>c</code> and <code>m</code>
- * are the settings for current and max value.</i>
- * <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate.
- * <li> --nice-name=<i>nice name to appear in ps</i>
- * <li> --package-name=<i>package name this process belongs to</i>
- * <li> --runtime-args indicates that the remaining arg list should
- * be handed off to com.android.internal.os.RuntimeInit, rather than
- * processed directly.
- * Android runtime startup (eg, Binder initialization) is also eschewed.
- * <li> [--] <args for RuntimeInit >
- * </ul>
- */
- static class Arguments {
- /** from --setuid */
- int uid = 0;
- boolean uidSpecified;
-
- /** from --setgid */
- int gid = 0;
- boolean gidSpecified;
-
- /** from --setgroups */
- int[] gids;
-
- /**
- * From --runtime-flags.
- */
- int runtimeFlags;
-
- /** From --mount-external */
- int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
-
- /** from --target-sdk-version. */
- int targetSdkVersion;
- boolean targetSdkVersionSpecified;
-
- /** from --nice-name */
- String niceName;
-
- /** from --capabilities */
- boolean capabilitiesSpecified;
- long permittedCapabilities;
- long effectiveCapabilities;
-
- /** from --seinfo */
- boolean seInfoSpecified;
- String seInfo;
-
- /** from all --rlimit=r,c,m */
- ArrayList<int[]> rlimits;
-
- /** from --invoke-with */
- String invokeWith;
-
- /** from --package-name */
- String packageName;
-
- /** from --packages-for-uid */
- String[] packagesForUid;
-
- /** from --visible-vols */
- String[] visibleVolIds;
-
- /**
- * Any args after and including the first non-option arg
- * (or after a '--')
- */
- String remainingArgs[];
-
- /**
- * Whether the current arguments constitute an ABI list query.
- */
- boolean abiListQuery;
-
- /**
- * The instruction set to use, or null when not important.
- */
- String instructionSet;
-
- /**
- * The app data directory. May be null, e.g., for the system server. Note that this might
- * not be reliable in the case of process-sharing apps.
- */
- String appDataDir;
-
- /**
- * The APK path of the package to preload, when using --preload-package.
- */
- String preloadPackage;
-
- /**
- * A Base64 string representing a serialize ApplicationInfo Parcel,
- when using --preload-app.
- */
- String mPreloadApp;
-
- /**
- * The native library path of the package to preload, when using --preload-package.
- */
- String preloadPackageLibs;
-
- /**
- * The filename of the native library to preload, when using --preload-package.
- */
- String preloadPackageLibFileName;
-
- /**
- * The cache key under which to enter the preloaded package into the classloader cache,
- * when using --preload-package.
- */
- String preloadPackageCacheKey;
-
- /**
- * Whether this is a request to start preloading the default resources and classes.
- * This argument only makes sense when the zygote is in lazy preload mode (i.e, when
- * it's started with --enable-lazy-preload).
- */
- boolean preloadDefault;
-
- /**
- * Whether this is a request to start a zygote process as a child of this zygote.
- * Set with --start-child-zygote. The remaining arguments must include the
- * CHILD_ZYGOTE_SOCKET_NAME_ARG flag to indicate the abstract socket name that
- * should be used for communication.
- */
- boolean startChildZygote;
-
- /**
- * Whether the current arguments constitute a request for the zygote's PID.
- */
- boolean pidQuery;
-
- /**
- * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time,
- * or when they change, via --set-api-blacklist-exemptions.
- */
- String[] apiBlacklistExemptions;
-
- /**
- * Sampling rate for logging hidden API accesses to the event log. This is sent to the
- * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate.
- */
- int hiddenApiAccessLogSampleRate = -1;
-
- /**
- * Constructs instance and parses args
- * @param args zygote command-line args
- * @throws IllegalArgumentException
- */
- Arguments(String args[]) throws IllegalArgumentException {
- parseArgs(args);
- }
-
- /**
- * Parses the commandline arguments intended for the Zygote spawner
- * (such as "--setuid=" and "--setgid=") and creates an array
- * containing the remaining args.
- *
- * Per security review bug #1112214, duplicate args are disallowed in
- * critical cases to make injection harder.
- */
- private void parseArgs(String args[])
- throws IllegalArgumentException {
- int curArg = 0;
-
- boolean seenRuntimeArgs = false;
-
- boolean expectRuntimeArgs = true;
- for ( /* curArg */ ; curArg < args.length; curArg++) {
- String arg = args[curArg];
-
- if (arg.equals("--")) {
- curArg++;
- break;
- } else if (arg.startsWith("--setuid=")) {
- if (uidSpecified) {
- throw new IllegalArgumentException(
- "Duplicate arg specified");
- }
- uidSpecified = true;
- uid = Integer.parseInt(
- arg.substring(arg.indexOf('=') + 1));
- } else if (arg.startsWith("--setgid=")) {
- if (gidSpecified) {
- throw new IllegalArgumentException(
- "Duplicate arg specified");
- }
- gidSpecified = true;
- gid = Integer.parseInt(
- arg.substring(arg.indexOf('=') + 1));
- } else if (arg.startsWith("--target-sdk-version=")) {
- if (targetSdkVersionSpecified) {
- throw new IllegalArgumentException(
- "Duplicate target-sdk-version specified");
- }
- targetSdkVersionSpecified = true;
- targetSdkVersion = Integer.parseInt(
- arg.substring(arg.indexOf('=') + 1));
- } else if (arg.equals("--runtime-args")) {
- seenRuntimeArgs = true;
- } else if (arg.startsWith("--runtime-flags=")) {
- runtimeFlags = Integer.parseInt(
- arg.substring(arg.indexOf('=') + 1));
- } else if (arg.startsWith("--seinfo=")) {
- if (seInfoSpecified) {
- throw new IllegalArgumentException(
- "Duplicate arg specified");
- }
- seInfoSpecified = true;
- seInfo = arg.substring(arg.indexOf('=') + 1);
- } else if (arg.startsWith("--capabilities=")) {
- if (capabilitiesSpecified) {
- throw new IllegalArgumentException(
- "Duplicate arg specified");
- }
- capabilitiesSpecified = true;
- String capString = arg.substring(arg.indexOf('=')+1);
-
- String[] capStrings = capString.split(",", 2);
-
- if (capStrings.length == 1) {
- effectiveCapabilities = Long.decode(capStrings[0]);
- permittedCapabilities = effectiveCapabilities;
- } else {
- permittedCapabilities = Long.decode(capStrings[0]);
- effectiveCapabilities = Long.decode(capStrings[1]);
- }
- } else if (arg.startsWith("--rlimit=")) {
- // Duplicate --rlimit arguments are specifically allowed.
- String[] limitStrings
- = arg.substring(arg.indexOf('=')+1).split(",");
-
- if (limitStrings.length != 3) {
- throw new IllegalArgumentException(
- "--rlimit= should have 3 comma-delimited ints");
- }
- int[] rlimitTuple = new int[limitStrings.length];
-
- for(int i=0; i < limitStrings.length; i++) {
- rlimitTuple[i] = Integer.parseInt(limitStrings[i]);
- }
-
- if (rlimits == null) {
- rlimits = new ArrayList();
- }
-
- rlimits.add(rlimitTuple);
- } else if (arg.startsWith("--setgroups=")) {
- if (gids != null) {
- throw new IllegalArgumentException(
- "Duplicate arg specified");
- }
-
- String[] params
- = arg.substring(arg.indexOf('=') + 1).split(",");
-
- gids = new int[params.length];
-
- for (int i = params.length - 1; i >= 0 ; i--) {
- gids[i] = Integer.parseInt(params[i]);
- }
- } else if (arg.equals("--invoke-with")) {
- if (invokeWith != null) {
- throw new IllegalArgumentException(
- "Duplicate arg specified");
- }
- try {
- invokeWith = args[++curArg];
- } catch (IndexOutOfBoundsException ex) {
- throw new IllegalArgumentException(
- "--invoke-with requires argument");
- }
- } else if (arg.startsWith("--nice-name=")) {
- if (niceName != null) {
- throw new IllegalArgumentException(
- "Duplicate arg specified");
- }
- niceName = arg.substring(arg.indexOf('=') + 1);
- } else if (arg.equals("--mount-external-default")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
- } else if (arg.equals("--mount-external-read")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_READ;
- } else if (arg.equals("--mount-external-write")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_WRITE;
- } else if (arg.equals("--mount-external-full")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_FULL;
- } else if (arg.equals("--mount-external-installer")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER;
- } else if (arg.equals("--mount-external-legacy")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_LEGACY;
- } else if (arg.equals("--query-abi-list")) {
- abiListQuery = true;
- } else if (arg.equals("--get-pid")) {
- pidQuery = true;
- } else if (arg.startsWith("--instruction-set=")) {
- instructionSet = arg.substring(arg.indexOf('=') + 1);
- } else if (arg.startsWith("--app-data-dir=")) {
- appDataDir = arg.substring(arg.indexOf('=') + 1);
- } else if (arg.equals("--preload-app")) {
- mPreloadApp = args[++curArg];
- } else if (arg.equals("--preload-package")) {
- preloadPackage = args[++curArg];
- preloadPackageLibs = args[++curArg];
- preloadPackageLibFileName = args[++curArg];
- preloadPackageCacheKey = args[++curArg];
- } else if (arg.equals("--preload-default")) {
- preloadDefault = true;
- expectRuntimeArgs = false;
- } else if (arg.equals("--start-child-zygote")) {
- startChildZygote = true;
- } else if (arg.equals("--set-api-blacklist-exemptions")) {
- // consume all remaining args; this is a stand-alone command, never included
- // with the regular fork command.
- apiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length);
- curArg = args.length;
- expectRuntimeArgs = false;
- } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) {
- String rateStr = arg.substring(arg.indexOf('=') + 1);
- try {
- hiddenApiAccessLogSampleRate = Integer.parseInt(rateStr);
- } catch (NumberFormatException nfe) {
- throw new IllegalArgumentException(
- "Invalid log sampling rate: " + rateStr, nfe);
- }
- expectRuntimeArgs = false;
- } else if (arg.startsWith("--package-name=")) {
- if (packageName != null) {
- throw new IllegalArgumentException("Duplicate arg specified");
- }
- packageName = arg.substring(arg.indexOf('=') + 1);
- } else if (arg.startsWith("--packages-for-uid=")) {
- packagesForUid = arg.substring(arg.indexOf('=') + 1).split(",");
- } else if (arg.startsWith("--visible-vols=")) {
- visibleVolIds = arg.substring(arg.indexOf('=') + 1).split(",");
- } else {
- break;
- }
- }
-
- if (abiListQuery || pidQuery) {
- if (args.length - curArg > 0) {
- throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
- }
- } else if (preloadPackage != null) {
- if (args.length - curArg > 0) {
- throw new IllegalArgumentException(
- "Unexpected arguments after --preload-package.");
- }
- } else if (mPreloadApp != null) {
- if (args.length - curArg > 0) {
- throw new IllegalArgumentException(
- "Unexpected arguments after --preload-app.");
- }
- } else if (expectRuntimeArgs) {
- if (!seenRuntimeArgs) {
- throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
- }
-
- remainingArgs = new String[args.length - curArg];
- System.arraycopy(args, curArg, remainingArgs, 0, remainingArgs.length);
- }
-
- if (startChildZygote) {
- boolean seenChildSocketArg = false;
- for (String arg : remainingArgs) {
- if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
- seenChildSocketArg = true;
- break;
- }
- }
- if (!seenChildSocketArg) {
- throw new IllegalArgumentException("--start-child-zygote specified " +
- "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG);
- }
- }
- }
- }
-
- /**
- * Reads an argument list from the command socket/
- * @return Argument list or null if EOF is reached
- * @throws IOException passed straight through
- */
- private String[] readArgumentList()
- throws IOException {
-
- /**
- * See android.os.Process.zygoteSendArgsAndGetPid()
- * Presently the wire format to the zygote process is:
- * a) a count of arguments (argc, in essence)
- * b) a number of newline-separated argument strings equal to count
- *
- * After the zygote process reads these it will write the pid of
- * the child or -1 on failure.
- */
-
- int argc;
-
- try {
- String s = mSocketReader.readLine();
-
- if (s == null) {
- // EOF reached.
- return null;
- }
- argc = Integer.parseInt(s);
- } catch (NumberFormatException ex) {
- Log.e(TAG, "invalid Zygote wire format: non-int at argc");
- throw new IOException("invalid wire format");
- }
-
- // See bug 1092107: large argc can be used for a DOS attack
- if (argc > MAX_ZYGOTE_ARGC) {
- throw new IOException("max arg count exceeded");
- }
-
- String[] result = new String[argc];
- for (int i = 0; i < argc; i++) {
- result[i] = mSocketReader.readLine();
- if (result[i] == null) {
- // We got an unexpected EOF.
- throw new IOException("truncated request");
- }
- }
-
- return result;
- }
-
- /**
- * uid 1000 (Process.SYSTEM_UID) may specify any uid > 1000 in normal
- * operation. It may also specify any gid and setgroups() list it chooses.
- * In factory test mode, it may specify any UID.
- *
- * @param args non-null; zygote spawner arguments
- * @param peer non-null; peer credentials
- * @throws ZygoteSecurityException
- */
- private static void applyUidSecurityPolicy(Arguments args, Credentials peer)
- throws ZygoteSecurityException {
-
- if (peer.getUid() == Process.SYSTEM_UID) {
- /* In normal operation, SYSTEM_UID can only specify a restricted
- * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
- */
- boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF;
-
- if (uidRestricted && args.uidSpecified && (args.uid < Process.SYSTEM_UID)) {
- throw new ZygoteSecurityException(
- "System UID may not launch process with UID < "
- + Process.SYSTEM_UID);
- }
- }
-
- // If not otherwise specified, uid and gid are inherited from peer
- if (!args.uidSpecified) {
- args.uid = peer.getUid();
- args.uidSpecified = true;
- }
- if (!args.gidSpecified) {
- args.gid = peer.getGid();
- args.gidSpecified = true;
- }
- }
-
- /**
- * Applies debugger system properties to the zygote arguments.
- *
- * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
- * the debugger state is specified via the "--enable-jdwp" flag
- * in the spawn request.
- *
- * @param args non-null; zygote spawner args
- */
- public static void applyDebuggerSystemProperty(Arguments args) {
- if (RoSystemProperties.DEBUGGABLE) {
- args.runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
- }
- }
-
- /**
- * Applies zygote security policy.
- * Based on the credentials of the process issuing a zygote command:
- * <ol>
- * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a
- * wrapper command.
- * <li> Any other uid may not specify any invoke-with argument.
- * </ul>
- *
- * @param args non-null; zygote spawner arguments
- * @param peer non-null; peer credentials
- * @throws ZygoteSecurityException
- */
- private static void applyInvokeWithSecurityPolicy(Arguments args, Credentials peer)
- throws ZygoteSecurityException {
- int peerUid = peer.getUid();
-
- if (args.invokeWith != null && peerUid != 0 &&
- (args.runtimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) {
- throw new ZygoteSecurityException("Peer is permitted to specify an"
- + "explicit invoke-with wrapper command only for debuggable"
- + "applications.");
- }
- }
-
- /**
- * Applies invoke-with system properties to the zygote arguments.
- *
- * @param args non-null; zygote args
- */
- public static void applyInvokeWithSystemProperty(Arguments args) {
- if (args.invokeWith == null && args.niceName != null) {
- String property = "wrap." + args.niceName;
- args.invokeWith = SystemProperties.get(property);
- if (args.invokeWith != null && args.invokeWith.length() == 0) {
- args.invokeWith = null;
- }
- }
- }
-
- /**
* Handles post-fork setup of child proc, closing sockets as appropriate,
* reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller
* if successful or returning if failed.
@@ -931,7 +390,7 @@
* @param pipeFd null-ok; pipe for communication back to Zygote.
* @param isZygote whether this new child process is itself a new Zygote.
*/
- private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
+ private Runnable handleChildProc(ZygoteArguments parsedArgs, FileDescriptor[] descriptors,
FileDescriptor pipeFd, boolean isZygote) {
/**
* By the time we get here, the native code has closed the two actual Zygote
@@ -954,27 +413,27 @@
}
}
- if (parsedArgs.niceName != null) {
- Process.setArgV0(parsedArgs.niceName);
+ if (parsedArgs.mNiceName != null) {
+ Process.setArgV0(parsedArgs.mNiceName);
}
// End of the postFork event.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- if (parsedArgs.invokeWith != null) {
- WrapperInit.execApplication(parsedArgs.invokeWith,
- parsedArgs.niceName, parsedArgs.targetSdkVersion,
+ if (parsedArgs.mInvokeWith != null) {
+ WrapperInit.execApplication(parsedArgs.mInvokeWith,
+ parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion,
VMRuntime.getCurrentInstructionSet(),
- pipeFd, parsedArgs.remainingArgs);
+ pipeFd, parsedArgs.mRemainingArgs);
// Should not get here.
throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
} else {
if (!isZygote) {
- return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
- null /* classLoader */);
+ return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
+ parsedArgs.mRemainingArgs, null /* classLoader */);
} else {
- return ZygoteInit.childZygoteInit(parsedArgs.targetSdkVersion,
- parsedArgs.remainingArgs, null /* classLoader */);
+ return ZygoteInit.childZygoteInit(parsedArgs.mTargetSdkVersion,
+ parsedArgs.mRemainingArgs, null /* classLoader */);
}
}
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index c2c6ae6..e3e55ed 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -21,7 +21,6 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.opengl.EGL14;
import android.os.Build;
import android.os.Environment;
import android.os.IInstalld;
@@ -71,16 +70,16 @@
/**
* Startup class for the zygote process.
*
- * Pre-initializes some classes, and then waits for commands on a UNIX domain
- * socket. Based on these commands, forks off child processes that inherit
- * the initial state of the VM.
+ * Pre-initializes some classes, and then waits for commands on a UNIX domain socket. Based on these
+ * commands, forks off child processes that inherit the initial state of the VM.
*
- * Please see {@link ZygoteConnection.Arguments} for documentation on the
- * client protocol.
+ * Please see {@link ZygoteArguments} for documentation on the client protocol.
*
* @hide
*/
public class ZygoteInit {
+
+ // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate
private static final String TAG = "Zygote";
private static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload";
@@ -89,11 +88,15 @@
private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020;
private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030;
- /** when preloading, GC after allocating this many bytes */
+ /**
+ * when preloading, GC after allocating this many bytes
+ */
private static final int PRELOAD_GC_THRESHOLD = 50000;
private static final String ABI_LIST_ARG = "--abi-list=";
+ // TODO (chriswailes): Re-name this --zygote-socket-name= and then add a
+ // --blastula-socket-name parameter.
private static final String SOCKET_NAME_ARG = "--socket-name=";
/**
@@ -106,7 +109,9 @@
*/
private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";
- /** Controls whether we should preload resources during zygote init. */
+ /**
+ * Controls whether we should preload resources during zygote init.
+ */
public static final boolean PRELOAD_RESOURCES = true;
private static final int UNPRIVILEGED_UID = 9999;
@@ -173,6 +178,7 @@
}
native private static void nativePreloadAppProcessHALs();
+
native private static void nativePreloadOpenGL();
private static void preloadOpenGL() {
@@ -191,8 +197,8 @@
/**
* Register AndroidKeyStoreProvider and warm up the providers that are already registered.
*
- * By doing it here we avoid that each app does it when requesting a service from the
- * provider for the first time.
+ * By doing it here we avoid that each app does it when requesting a service from the provider
+ * for the first time.
*/
private static void warmUpJcaProviders() {
long startTime = SystemClock.uptimeMillis();
@@ -218,11 +224,10 @@
}
/**
- * Performs Zygote process initialization. Loads and initializes
- * commonly used classes.
+ * Performs Zygote process initialization. Loads and initializes commonly used classes.
*
- * Most classes only cause a few hundred bytes to be allocated, but
- * a few will allocate a dozen Kbytes (in one case, 500+K).
+ * Most classes only cause a few hundred bytes to be allocated, but a few will allocate a dozen
+ * Kbytes (in one case, 500+K).
*/
private static void preloadClasses() {
final VMRuntime runtime = VMRuntime.getRuntime();
@@ -263,8 +268,8 @@
runtime.setTargetHeapUtilization(0.8f);
try {
- BufferedReader br
- = new BufferedReader(new InputStreamReader(is), 256);
+ BufferedReader br =
+ new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);
int count = 0;
String line;
@@ -305,7 +310,7 @@
}
Log.i(TAG, "...preloaded " + count + " classes in "
- + (SystemClock.uptimeMillis()-startTime) + "ms.");
+ + (SystemClock.uptimeMillis() - startTime) + "ms.");
} catch (IOException e) {
Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
} finally {
@@ -331,11 +336,10 @@
}
/**
- * Load in commonly used resources, so they can be shared across
- * processes.
+ * Load in commonly used resources, so they can be shared across processes.
*
- * These tend to be a few Kbytes, but are frequently in the 20-40K
- * range, and occasionally even larger.
+ * These tend to be a few Kbytes, but are frequently in the 20-40K range, and occasionally even
+ * larger.
*/
private static void preloadResources() {
final VMRuntime runtime = VMRuntime.getRuntime();
@@ -352,7 +356,7 @@
int N = preloadDrawables(ar);
ar.recycle();
Log.i(TAG, "...preloaded " + N + " resources in "
- + (SystemClock.uptimeMillis()-startTime) + "ms.");
+ + (SystemClock.uptimeMillis() - startTime) + "ms.");
startTime = SystemClock.uptimeMillis();
ar = mResources.obtainTypedArray(
@@ -360,7 +364,7 @@
N = preloadColorStateLists(ar);
ar.recycle();
Log.i(TAG, "...preloaded " + N + " resources in "
- + (SystemClock.uptimeMillis()-startTime) + "ms.");
+ + (SystemClock.uptimeMillis() - startTime) + "ms.");
if (mResources.getBoolean(
com.android.internal.R.bool.config_freeformWindowManagement)) {
@@ -381,7 +385,7 @@
private static int preloadColorStateLists(TypedArray ar) {
int N = ar.length();
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
int id = ar.getResourceId(i, 0);
if (false) {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
@@ -390,8 +394,8 @@
if (mResources.getColorStateList(id, null) == null) {
throw new IllegalArgumentException(
"Unable to find preloaded color resource #0x"
- + Integer.toHexString(id)
- + " (" + ar.getString(i) + ")");
+ + Integer.toHexString(id)
+ + " (" + ar.getString(i) + ")");
}
}
}
@@ -401,7 +405,7 @@
private static int preloadDrawables(TypedArray ar) {
int N = ar.length();
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
int id = ar.getResourceId(i, 0);
if (false) {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
@@ -410,8 +414,8 @@
if (mResources.getDrawable(id, null) == null) {
throw new IllegalArgumentException(
"Unable to find preloaded drawable resource #0x"
- + Integer.toHexString(id)
- + " (" + ar.getString(i) + ")");
+ + Integer.toHexString(id)
+ + " (" + ar.getString(i) + ")");
}
}
}
@@ -419,9 +423,8 @@
}
/**
- * Runs several special GCs to try to clean up a few generations of
- * softly- and final-reachable objects, along with any other garbage.
- * This is only useful just before a fork().
+ * Runs several special GCs to try to clean up a few generations of softly- and final-reachable
+ * objects, along with any other garbage. This is only useful just before a fork().
*/
private static void gcAndFinalize() {
ZygoteHooks.gcAndFinalize();
@@ -430,12 +433,12 @@
/**
* Finish remaining work for the newly forked system server process.
*/
- private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
+ private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) {
// set umask to 0077 so new files and directories will default to owner-only permissions.
Os.umask(S_IRWXG | S_IRWXO);
- if (parsedArgs.niceName != null) {
- Process.setArgV0(parsedArgs.niceName);
+ if (parsedArgs.mNiceName != null) {
+ Process.setArgV0(parsedArgs.mNiceName);
}
final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
@@ -454,8 +457,8 @@
}
}
- if (parsedArgs.invokeWith != null) {
- String[] args = parsedArgs.remainingArgs;
+ if (parsedArgs.mInvokeWith != null) {
+ String[] args = parsedArgs.mRemainingArgs;
// If we have a non-null system server class path, we'll have to duplicate the
// existing arguments and append the classpath to it. ART will handle the classpath
// correctly when we exec a new process.
@@ -467,15 +470,15 @@
args = amendedArgs;
}
- WrapperInit.execApplication(parsedArgs.invokeWith,
- parsedArgs.niceName, parsedArgs.targetSdkVersion,
+ WrapperInit.execApplication(parsedArgs.mInvokeWith,
+ parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion,
VMRuntime.getCurrentInstructionSet(), null, args);
throw new IllegalStateException("Unexpected return from WrapperInit.execApplication");
} else {
ClassLoader cl = null;
if (systemServerClasspath != null) {
- cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
+ cl = createPathClassLoader(systemServerClasspath, parsedArgs.mTargetSdkVersion);
Thread.currentThread().setContextClassLoader(cl);
}
@@ -483,16 +486,17 @@
/*
* Pass the remaining arguments to SystemServer.
*/
- return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
+ return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
+ parsedArgs.mRemainingArgs, cl);
}
/* should never reach here */
}
/**
- * Note that preparing the profiles for system server does not require special
- * selinux permissions. From the installer perspective the system server is a regular package
- * which can capture profile information.
+ * Note that preparing the profiles for system server does not require special selinux
+ * permissions. From the installer perspective the system server is a regular package which can
+ * capture profile information.
*/
private static void prepareSystemServerProfile(String systemServerClasspath)
throws RemoteException {
@@ -544,8 +548,8 @@
}
/**
- * Performs dex-opt on the elements of {@code classPath}, if needed. We
- * choose the instruction set of the current runtime.
+ * Performs dex-opt on the elements of {@code classPath}, if needed. We choose the instruction
+ * set of the current runtime.
*/
private static void performSystemServerDexOpt(String classPath) {
final String[] classPathElements = classPath.split(":");
@@ -563,8 +567,9 @@
int dexoptNeeded;
try {
dexoptNeeded = DexFile.getDexOptNeeded(
- classPathElement, instructionSet, systemServerFilter,
- null /* classLoaderContext */, false /* newProfile */, false /* downgrade */);
+ classPathElement, instructionSet, systemServerFilter,
+ null /* classLoaderContext */, false /* newProfile */,
+ false /* downgrade */);
} catch (FileNotFoundException ignored) {
// Do not add to the classpath.
Log.w(TAG, "Missing classpath element for system server: " + classPathElement);
@@ -607,8 +612,8 @@
}
/**
- * Encodes the system server class loader context in a format that is accepted by dexopt.
- * This assumes the system server is always loaded with a {@link dalvik.system.PathClassLoader}.
+ * Encodes the system server class loader context in a format that is accepted by dexopt. This
+ * assumes the system server is always loaded with a {@link dalvik.system.PathClassLoader}.
*
* Note that ideally we would use the {@code DexoptUtils} to compute this. However we have no
* dependency here on the server so we hard code the logic again.
@@ -619,10 +624,11 @@
/**
* Encodes the class path in a format accepted by dexopt.
- * @param classPath the old class path (may be empty).
- * @param newElement the new class path elements
- * @return the class path encoding resulted from appending {@code newElement} to
- * {@code classPath}.
+ *
+ * @param classPath The old class path (may be empty).
+ * @param newElement The new class path elements
+ * @return The class path encoding resulted from appending {@code newElement} to {@code
+ * classPath}.
*/
private static String encodeSystemServerClassPath(String classPath, String newElement) {
return (classPath == null || classPath.isEmpty())
@@ -633,25 +639,25 @@
/**
* Prepare the arguments and forks for the system server process.
*
- * Returns an {@code Runnable} that provides an entrypoint into system_server code in the
- * child process, and {@code null} in the parent.
+ * @return A {@code Runnable} that provides an entrypoint into system_server code in the child
+ * process; {@code null} in the parent.
*/
private static Runnable forkSystemServer(String abiList, String socketName,
ZygoteServer zygoteServer) {
long capabilities = posixCapabilitiesAsBits(
- OsConstants.CAP_IPC_LOCK,
- OsConstants.CAP_KILL,
- OsConstants.CAP_NET_ADMIN,
- OsConstants.CAP_NET_BIND_SERVICE,
- OsConstants.CAP_NET_BROADCAST,
- OsConstants.CAP_NET_RAW,
- OsConstants.CAP_SYS_MODULE,
- OsConstants.CAP_SYS_NICE,
- OsConstants.CAP_SYS_PTRACE,
- OsConstants.CAP_SYS_TIME,
- OsConstants.CAP_SYS_TTY_CONFIG,
- OsConstants.CAP_WAKE_ALARM,
- OsConstants.CAP_BLOCK_SUSPEND
+ OsConstants.CAP_IPC_LOCK,
+ OsConstants.CAP_KILL,
+ OsConstants.CAP_NET_ADMIN,
+ OsConstants.CAP_NET_BIND_SERVICE,
+ OsConstants.CAP_NET_BROADCAST,
+ OsConstants.CAP_NET_RAW,
+ OsConstants.CAP_SYS_MODULE,
+ OsConstants.CAP_SYS_NICE,
+ OsConstants.CAP_SYS_PTRACE,
+ OsConstants.CAP_SYS_TIME,
+ OsConstants.CAP_SYS_TTY_CONFIG,
+ OsConstants.CAP_WAKE_ALARM,
+ OsConstants.CAP_BLOCK_SUSPEND
);
/* Containers run without some capabilities, so drop any caps that are not available. */
StructCapUserHeader header = new StructCapUserHeader(
@@ -666,38 +672,39 @@
/* Hardcoded command line to start the system server */
String args[] = {
- "--setuid=1000",
- "--setgid=1000",
- "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1024,1032,1065,3001,3002,3003,3006,3007,3009,3010",
- "--capabilities=" + capabilities + "," + capabilities,
- "--nice-name=system_server",
- "--runtime-args",
- "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
- "com.android.server.SystemServer",
+ "--setuid=1000",
+ "--setgid=1000",
+ "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
+ + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010",
+ "--capabilities=" + capabilities + "," + capabilities,
+ "--nice-name=system_server",
+ "--runtime-args",
+ "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
+ "com.android.server.SystemServer",
};
- ZygoteConnection.Arguments parsedArgs = null;
+ ZygoteArguments parsedArgs = null;
int pid;
try {
- parsedArgs = new ZygoteConnection.Arguments(args);
- ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
- ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
+ parsedArgs = new ZygoteArguments(args);
+ Zygote.applyDebuggerSystemProperty(parsedArgs);
+ Zygote.applyInvokeWithSystemProperty(parsedArgs);
boolean profileSystemServer = SystemProperties.getBoolean(
"dalvik.vm.profilesystemserver", false);
if (profileSystemServer) {
- parsedArgs.runtimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
+ parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
}
/* Request to fork the system server process */
pid = Zygote.forkSystemServer(
- parsedArgs.uid, parsedArgs.gid,
- parsedArgs.gids,
- parsedArgs.runtimeFlags,
+ parsedArgs.mUid, parsedArgs.mGid,
+ parsedArgs.mGids,
+ parsedArgs.mRuntimeFlags,
null,
- parsedArgs.permittedCapabilities,
- parsedArgs.effectiveCapabilities);
+ parsedArgs.mPermittedCapabilities,
+ parsedArgs.mEffectiveCapabilities);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}
@@ -743,7 +750,7 @@
throw new RuntimeException("Failed to setpgid(0,0)", ex);
}
- final Runnable caller;
+ Runnable caller;
try {
// Report Zygote start time to tron unless it is a runtime restart
if (!"1".equals(SystemProperties.get("sys.boot_completed"))) {
@@ -779,16 +786,26 @@
throw new RuntimeException("No ABI list supplied.");
}
- zygoteServer.registerServerSocketFromEnv(socketName);
+ // TODO (chriswailes): Wrap these three calls in a helper function?
+ final String blastulaSocketName =
+ socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME)
+ ? ZygoteProcess.BLASTULA_POOL_SOCKET_NAME
+ : ZygoteProcess.BLASTULA_POOL_SECONDARY_SOCKET_NAME;
+
+ zygoteServer.createZygoteSocket(socketName);
+ Zygote.createBlastulaSocket(blastulaSocketName);
+
+ Zygote.getSocketFDs(socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME));
+
// In some configurations, we avoid preloading resources and classes eagerly.
// In such cases, we will preload things prior to our first fork.
if (!enableLazyPreload) {
bootTimingsTraceLog.traceBegin("ZygotePreload");
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
- SystemClock.uptimeMillis());
+ SystemClock.uptimeMillis());
preload(bootTimingsTraceLog);
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
- SystemClock.uptimeMillis());
+ SystemClock.uptimeMillis());
bootTimingsTraceLog.traceEnd(); // ZygotePreload
} else {
Zygote.resetNicePriority();
@@ -822,11 +839,18 @@
}
}
- Log.i(TAG, "Accepting command socket connections");
+ // If the return value is null then this is the zygote process
+ // returning to the normal control flow. If it returns a Runnable
+ // object then this is a blastula that has finished specializing.
+ caller = Zygote.initBlastulaPool();
- // The select loop returns early in the child process after a fork and
- // loops forever in the zygote.
- caller = zygoteServer.runSelectLoop(abiList);
+ if (caller == null) {
+ Log.i(TAG, "Accepting command socket connections");
+
+ // The select loop returns early in the child process after a fork and
+ // loops forever in the zygote.
+ caller = zygoteServer.runSelectLoop(abiList);
+ }
} catch (Throwable ex) {
Log.e(TAG, "System zygote died with exception", ex);
throw ex;
@@ -844,17 +868,16 @@
/**
* Return {@code true} if this device configuration has another zygote.
*
- * We determine this by comparing the device ABI list with this zygotes
- * list. If this zygote supports all ABIs this device supports, there won't
- * be another zygote.
+ * We determine this by comparing the device ABI list with this zygotes list. If this zygote
+ * supports all ABIs this device supports, there won't be another zygote.
*/
private static boolean hasSecondZygote(String abiList) {
return !SystemProperties.get("ro.product.cpu.abilist").equals(abiList);
}
private static void waitForSecondaryZygote(String socketName) {
- String otherZygoteName = Process.ZYGOTE_SOCKET.equals(socketName) ?
- Process.SECONDARY_ZYGOTE_SOCKET : Process.ZYGOTE_SOCKET;
+ String otherZygoteName = ZygoteProcess.ZYGOTE_SOCKET_NAME.equals(socketName)
+ ? ZygoteProcess.ZYGOTE_SECONDARY_SOCKET_NAME : ZygoteProcess.ZYGOTE_SOCKET_NAME;
ZygoteProcess.waitForConnectionToZygote(otherZygoteName);
}
@@ -869,9 +892,8 @@
}
/**
- * The main function called when started through the zygote process. This
- * could be unified with main(), if the native code in nativeFinishInit()
- * were rationalized with Zygote startup.<p>
+ * The main function called when started through the zygote process. This could be unified with
+ * main(), if the native code in nativeFinishInit() were rationalized with Zygote startup.<p>
*
* Current recognized args:
* <ul>
@@ -881,7 +903,8 @@
* @param targetSdkVersion target SDK version
* @param argv arg strings
*/
- public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
+ public static final Runnable zygoteInit(int targetSdkVersion, String[] argv,
+ ClassLoader classLoader) {
if (RuntimeInit.DEBUG) {
Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
}
@@ -895,9 +918,9 @@
}
/**
- * The main function called when starting a child zygote process. This is used as an
- * alternative to zygoteInit(), which skips calling into initialization routines that
- * start the Binder threadpool.
+ * The main function called when starting a child zygote process. This is used as an alternative
+ * to zygoteInit(), which skips calling into initialization routines that start the Binder
+ * threadpool.
*/
static final Runnable childZygoteInit(
int targetSdkVersion, String[] argv, ClassLoader classLoader) {
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index fecf9b9..a78c095 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -20,14 +20,16 @@
import android.net.LocalServerSocket;
import android.net.LocalSocket;
-import android.system.Os;
import android.system.ErrnoException;
+import android.system.Os;
import android.system.StructPollfd;
import android.util.Log;
-
import android.util.Slog;
-import java.io.IOException;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
import java.io.FileDescriptor;
+import java.io.IOException;
import java.util.ArrayList;
/**
@@ -40,18 +42,17 @@
* client protocol.
*/
class ZygoteServer {
+ // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate
public static final String TAG = "ZygoteServer";
- private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
-
/**
* Listening socket that accepts new server connections.
*/
- private LocalServerSocket mServerSocket;
+ private LocalServerSocket mZygoteSocket;
/**
- * Whether or not mServerSocket's underlying FD should be closed directly.
- * If mServerSocket is created with an existing FD, closing the socket does
+ * Whether or not mZygoteSocket's underlying FD should be closed directly.
+ * If mZygoteSocket is created with an existing FD, closing the socket does
* not close the FD and it must be closed explicitly. If the socket is created
* with a name instead, then closing the socket will close the underlying FD
* and it should not be double-closed.
@@ -63,39 +64,24 @@
*/
private boolean mIsForkChild;
- ZygoteServer() {
- }
+ ZygoteServer() { }
void setForkChild() {
mIsForkChild = true;
}
/**
- * Registers a server socket for zygote command connections. This locates the server socket
- * file descriptor through an ANDROID_SOCKET_ environment variable.
+ * Creates a managed object representing the Zygote socket that has already
+ * been initialized and bound by init.
+ *
+ * TODO (chriswailes): Move the name selection logic into this function.
*
* @throws RuntimeException when open fails
*/
- void registerServerSocketFromEnv(String socketName) {
- if (mServerSocket == null) {
- int fileDesc;
- final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
- try {
- String env = System.getenv(fullSocketName);
- fileDesc = Integer.parseInt(env);
- } catch (RuntimeException ex) {
- throw new RuntimeException(fullSocketName + " unset or invalid", ex);
- }
-
- try {
- FileDescriptor fd = new FileDescriptor();
- fd.setInt$(fileDesc);
- mServerSocket = new LocalServerSocket(fd);
- mCloseSocketFd = true;
- } catch (IOException ex) {
- throw new RuntimeException(
- "Error binding to local socket '" + fileDesc + "'", ex);
- }
+ void createZygoteSocket(String socketName) {
+ if (mZygoteSocket == null) {
+ mZygoteSocket = Zygote.createManagedSocketFromInitSocket(socketName);
+ mCloseSocketFd = true;
}
}
@@ -104,9 +90,9 @@
* at the specified name in the abstract socket namespace.
*/
void registerServerSocketAtAbstractName(String socketName) {
- if (mServerSocket == null) {
+ if (mZygoteSocket == null) {
try {
- mServerSocket = new LocalServerSocket(socketName);
+ mZygoteSocket = new LocalServerSocket(socketName);
mCloseSocketFd = false;
} catch (IOException ex) {
throw new RuntimeException(
@@ -121,7 +107,7 @@
*/
private ZygoteConnection acceptCommandPeer(String abiList) {
try {
- return createNewConnection(mServerSocket.accept(), abiList);
+ return createNewConnection(mZygoteSocket.accept(), abiList);
} catch (IOException ex) {
throw new RuntimeException(
"IOException during accept()", ex);
@@ -139,9 +125,9 @@
*/
void closeServerSocket() {
try {
- if (mServerSocket != null) {
- FileDescriptor fd = mServerSocket.getFileDescriptor();
- mServerSocket.close();
+ if (mZygoteSocket != null) {
+ FileDescriptor fd = mZygoteSocket.getFileDescriptor();
+ mZygoteSocket.close();
if (fd != null && mCloseSocketFd) {
Os.close(fd);
}
@@ -152,7 +138,7 @@
Log.e(TAG, "Zygote: error closing descriptor", ex);
}
- mServerSocket = null;
+ mZygoteSocket = null;
}
/**
@@ -161,8 +147,8 @@
* closure after a child process is forked off.
*/
- FileDescriptor getServerSocketFileDescriptor() {
- return mServerSocket.getFileDescriptor();
+ FileDescriptor getZygoteSocketFileDescriptor() {
+ return mZygoteSocket.getFileDescriptor();
}
/**
@@ -171,36 +157,67 @@
* worth at a time.
*/
Runnable runSelectLoop(String abiList) {
- ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
+ ArrayList<FileDescriptor> socketFDs = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
- fds.add(mServerSocket.getFileDescriptor());
+ socketFDs.add(mZygoteSocket.getFileDescriptor());
peers.add(null);
while (true) {
- StructPollfd[] pollFds = new StructPollfd[fds.size()];
- for (int i = 0; i < pollFds.length; ++i) {
- pollFds[i] = new StructPollfd();
- pollFds[i].fd = fds.get(i);
- pollFds[i].events = (short) POLLIN;
+ int[] blastulaPipeFDs = Zygote.getBlastulaPipeFDs();
+
+ // Space for all of the socket FDs, the Blastula Pool Event FD, and
+ // all of the open blastula read pipe FDs.
+ StructPollfd[] pollFDs =
+ new StructPollfd[socketFDs.size() + 1 + blastulaPipeFDs.length];
+
+ int pollIndex = 0;
+ for (FileDescriptor socketFD : socketFDs) {
+ pollFDs[pollIndex] = new StructPollfd();
+ pollFDs[pollIndex].fd = socketFD;
+ pollFDs[pollIndex].events = (short) POLLIN;
+ ++pollIndex;
}
+
+ final int blastulaPoolEventFDIndex = pollIndex;
+ pollFDs[pollIndex] = new StructPollfd();
+ pollFDs[pollIndex].fd = Zygote.sBlastulaPoolEventFD;
+ pollFDs[pollIndex].events = (short) POLLIN;
+ ++pollIndex;
+
+ for (int blastulaPipeFD : blastulaPipeFDs) {
+ FileDescriptor managedFd = new FileDescriptor();
+ managedFd.setInt$(blastulaPipeFD);
+
+ pollFDs[pollIndex] = new StructPollfd();
+ pollFDs[pollIndex].fd = managedFd;
+ pollFDs[pollIndex].events = (short) POLLIN;
+ ++pollIndex;
+ }
+
try {
- Os.poll(pollFds, -1);
+ Os.poll(pollFDs, -1);
} catch (ErrnoException ex) {
throw new RuntimeException("poll failed", ex);
}
- for (int i = pollFds.length - 1; i >= 0; --i) {
- if ((pollFds[i].revents & POLLIN) == 0) {
+
+ while (--pollIndex >= 0) {
+ if ((pollFDs[pollIndex].revents & POLLIN) == 0) {
continue;
}
- if (i == 0) {
+ if (pollIndex == 0) {
+ // Zygote server socket
+
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
- fds.add(newPeer.getFileDesciptor());
- } else {
+ socketFDs.add(newPeer.getFileDescriptor());
+
+ } else if (pollIndex < blastulaPoolEventFDIndex) {
+ // Session socket accepted from the Zygote server socket
+
try {
- ZygoteConnection connection = peers.get(i);
+ ZygoteConnection connection = peers.get(pollIndex);
final Runnable command = connection.processOneCommand(this);
if (mIsForkChild) {
@@ -218,12 +235,12 @@
}
// We don't know whether the remote side of the socket was closed or
- // not until we attempt to read from it from processOneCommand. This shows up as
- // a regular POLLIN event in our regular processing loop.
+ // not until we attempt to read from it from processOneCommand. This
+ // shows up as a regular POLLIN event in our regular processing loop.
if (connection.isClosedByPeer()) {
connection.closeSocket();
- peers.remove(i);
- fds.remove(i);
+ peers.remove(pollIndex);
+ socketFDs.remove(pollIndex);
}
}
} catch (Exception e) {
@@ -235,13 +252,13 @@
Slog.e(TAG, "Exception executing zygote command: ", e);
- // Make sure the socket is closed so that the other end knows immediately
- // that something has gone wrong and doesn't time out waiting for a
- // response.
- ZygoteConnection conn = peers.remove(i);
+ // Make sure the socket is closed so that the other end knows
+ // immediately that something has gone wrong and doesn't time out
+ // waiting for a response.
+ ZygoteConnection conn = peers.remove(pollIndex);
conn.closeSocket();
- fds.remove(i);
+ socketFDs.remove(pollIndex);
} else {
// We're in the child so any exception caught here has happened post
// fork and before we execute ActivityThread.main (or any other main()
@@ -255,6 +272,55 @@
// is returned.
mIsForkChild = false;
}
+ } else {
+ // Either the blastula pool event FD or a blastula reporting pipe.
+
+ // If this is the event FD the payload will be the number of blastulas removed.
+ // If this is a reporting pipe FD the payload will be the PID of the blastula
+ // that was just specialized.
+ long messagePayload = -1;
+
+ try {
+ byte[] buffer = new byte[Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES];
+ int readBytes = Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length);
+
+ if (readBytes == Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES) {
+ DataInputStream inputStream =
+ new DataInputStream(new ByteArrayInputStream(buffer));
+
+ messagePayload = inputStream.readLong();
+ } else {
+ Log.e(TAG, "Incomplete read from blastula management FD of size "
+ + readBytes);
+ continue;
+ }
+ } catch (Exception ex) {
+ if (pollIndex == blastulaPoolEventFDIndex) {
+ Log.e(TAG, "Failed to read from blastula pool event FD: "
+ + ex.getMessage());
+ } else {
+ Log.e(TAG, "Failed to read from blastula reporting pipe: "
+ + ex.getMessage());
+ }
+
+ continue;
+ }
+
+ if (pollIndex > blastulaPoolEventFDIndex) {
+ Zygote.removeBlastulaTableEntry((int) messagePayload);
+ }
+
+ int[] sessionSocketRawFDs =
+ socketFDs.subList(1, socketFDs.size())
+ .stream()
+ .mapToInt(fd -> fd.getInt$())
+ .toArray();
+
+ final Runnable command = Zygote.fillBlastulaPool(sessionSocketRawFDs);
+
+ if (command != null) {
+ return command;
+ }
}
}
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 197e873..d61f10e 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -65,7 +65,7 @@
int dismissalSurface, int dismissalSentiment, in NotificationVisibility nv);
void onNotificationVisibilityChanged( in NotificationVisibility[] newlyVisibleKeys,
in NotificationVisibility[] noLongerVisibleKeys);
- void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded);
+ void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded, in int notificationLocation);
void onNotificationDirectReplied(String key);
void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount, int smartActionCount,
boolean generatedByAsssistant);
diff --git a/core/java/com/android/internal/statusbar/NotificationVisibility.java b/core/java/com/android/internal/statusbar/NotificationVisibility.java
index a7203e7..24bb789 100644
--- a/core/java/com/android/internal/statusbar/NotificationVisibility.java
+++ b/core/java/com/android/internal/statusbar/NotificationVisibility.java
@@ -21,6 +21,8 @@
import android.os.Parcelable;
import android.util.Log;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
import java.util.ArrayDeque;
import java.util.Collection;
@@ -33,18 +35,53 @@
public int rank;
public int count;
public boolean visible = true;
+ /** The visible location of the notification, could be e.g. notification shade or HUN. */
+ public NotificationLocation location;
/*package*/ int id;
+ /**
+ * The UI location of the notification.
+ *
+ * There is a one-to-one mapping between this enum and
+ * MetricsProto.MetricsEvent.NotificationLocation.
+ */
+ public enum NotificationLocation {
+ LOCATION_UNKNOWN(MetricsEvent.LOCATION_UNKNOWN),
+ LOCATION_FIRST_HEADS_UP(MetricsEvent.LOCATION_FIRST_HEADS_UP), // visible heads-up
+ LOCATION_HIDDEN_TOP(MetricsEvent.LOCATION_HIDDEN_TOP), // hidden/scrolled away on the top
+ LOCATION_MAIN_AREA(MetricsEvent.LOCATION_MAIN_AREA), // visible in the shade
+ // in the bottom stack, and peeking
+ LOCATION_BOTTOM_STACK_PEEKING(MetricsEvent.LOCATION_BOTTOM_STACK_PEEKING),
+ // in the bottom stack, and hidden
+ LOCATION_BOTTOM_STACK_HIDDEN(MetricsEvent.LOCATION_BOTTOM_STACK_HIDDEN),
+ LOCATION_GONE(MetricsEvent.LOCATION_GONE); // the view isn't laid out at all
+
+ private final int mMetricsEventNotificationLocation;
+
+ NotificationLocation(int metricsEventNotificationLocation) {
+ mMetricsEventNotificationLocation = metricsEventNotificationLocation;
+ }
+
+ /**
+ * Returns the field from MetricsEvent.NotificationLocation that corresponds to this object.
+ */
+ public int toMetricsEventEnum() {
+ return mMetricsEventNotificationLocation;
+ }
+ }
+
private NotificationVisibility() {
id = sNexrId++;
}
- private NotificationVisibility(String key, int rank, int count, boolean visibile) {
+ private NotificationVisibility(String key, int rank, int count, boolean visible,
+ NotificationLocation location) {
this();
this.key = key;
this.rank = rank;
this.count = count;
- this.visible = visibile;
+ this.visible = visible;
+ this.location = location;
}
@Override
@@ -54,12 +91,13 @@
+ " rank=" + rank
+ " count=" + count
+ (visible?" visible":"")
+ + " location=" + location.name()
+ " )";
}
@Override
public NotificationVisibility clone() {
- return obtain(this.key, this.rank, this.count, this.visible);
+ return obtain(this.key, this.rank, this.count, this.visible, this.location);
}
@Override
@@ -89,6 +127,7 @@
out.writeInt(this.rank);
out.writeInt(this.count);
out.writeInt(this.visible ? 1 : 0);
+ out.writeString(this.location.name());
}
private void readFromParcel(Parcel in) {
@@ -96,18 +135,28 @@
this.rank = in.readInt();
this.count = in.readInt();
this.visible = in.readInt() != 0;
+ this.location = NotificationLocation.valueOf(in.readString());
}
/**
- * Return a new NotificationVisibility instance from the global pool. Allows us to
- * avoid allocating new objects in many cases.
+ * Create a new NotificationVisibility object.
*/
public static NotificationVisibility obtain(String key, int rank, int count, boolean visible) {
+ return obtain(key, rank, count, visible,
+ NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
+ }
+
+ /**
+ * Create a new NotificationVisibility object.
+ */
+ public static NotificationVisibility obtain(String key, int rank, int count, boolean visible,
+ NotificationLocation location) {
NotificationVisibility vo = obtain();
vo.key = key;
vo.rank = rank;
vo.count = count;
vo.visible = visible;
+ vo.location = location;
return vo;
}
diff --git a/core/java/com/android/internal/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java
index 240c2e7..3260136 100644
--- a/core/java/com/android/internal/usb/DumpUtils.java
+++ b/core/java/com/android/internal/usb/DumpUtils.java
@@ -196,6 +196,15 @@
}
}
+ private static void writeContaminantPresenceStatus(@NonNull DualDumpOutputStream dump,
+ @NonNull String idName, long id, int contaminantPresenceStatus) {
+ if (dump.isProto()) {
+ dump.write(idName, id, contaminantPresenceStatus);
+ } else {
+ dump.write(idName, id,
+ UsbPort.contaminantPresenceStatusToString(contaminantPresenceStatus));
+ }
+ }
public static void writePortStatus(@NonNull DualDumpOutputStream dump, @NonNull String idName,
long id, @NonNull UsbPortStatus status) {
@@ -232,6 +241,10 @@
dump.end(roleCombinationToken);
}
+ writeContaminantPresenceStatus(dump, "contaminant_presence_status",
+ UsbPortStatusProto.CONTAMINANT_PRESENCE_STATUS,
+ status.getContaminantDetectionStatus());
+
dump.end(token);
}
}
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 605df04..f91b837 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -327,10 +327,6 @@
}
}
- public static @NonNull <T> List<T> defeatNullable(@Nullable List<T> val) {
- return (val != null) ? val : Collections.emptyList();
- }
-
/**
* @return the first element if not empty/null, null otherwise
*/
diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java
index 1aa32cc..276cad9 100644
--- a/core/java/com/android/internal/util/Protocol.java
+++ b/core/java/com/android/internal/util/Protocol.java
@@ -53,7 +53,6 @@
public static final int BASE_WIFI_PASSPOINT_MANAGER = 0x00028000;
public static final int BASE_WIFI_PASSPOINT_SERVICE = 0x00028100;
public static final int BASE_WIFI_LOGGER = 0x00028300;
- public static final int BASE_DHCP = 0x00030000;
public static final int BASE_DATA_CONNECTION = 0x00040000;
public static final int BASE_DATA_CONNECTION_AC = 0x00041000;
public static final int BASE_DATA_CONNECTION_TRACKER = 0x00042000;
@@ -62,7 +61,6 @@
public static final int BASE_NETWORK_STATE_TRACKER = 0x00070000;
public static final int BASE_CONNECTIVITY_MANAGER = 0x00080000;
public static final int BASE_NETWORK_AGENT = 0x00081000;
- public static final int BASE_NETWORK_MONITOR = 0x00082000;
public static final int BASE_NETWORK_FACTORY = 0x00083000;
public static final int BASE_ETHERNET = 0x00084000;
public static final int BASE_LOWPAN = 0x00085000;
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 356d178..0752efe 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -35,7 +35,6 @@
// TODO: Use ParceledListSlice instead
List<InputMethodInfo> getInputMethodList();
- List<InputMethodInfo> getVrInputMethodList();
// TODO: Use ParceledListSlice instead
List<InputMethodInfo> getEnabledInputMethodList();
List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId,
@@ -65,7 +64,6 @@
void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId);
boolean isInputMethodPickerShownForTest();
InputMethodSubtype getCurrentInputMethodSubtype();
- boolean setCurrentInputMethodSubtype(in InputMethodSubtype subtype);
void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
// This is kept due to @UnsupportedAppUsage.
// TODO(Bug 113914148): Consider removing this.
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index d5dc703..8d3c482 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -34,6 +34,7 @@
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.AsyncTask;
import android.os.Handler;
@@ -176,6 +177,7 @@
private UserManager mUserManager;
private final Handler mHandler;
private final SparseLongArray mLockoutDeadlines = new SparseLongArray();
+ private Boolean mHasSecureLockScreen;
/**
* Use {@link TrustManager#isTrustUsuallyManaged(int)}.
@@ -706,6 +708,10 @@
* @param userId the user whose pattern is to be saved.
*/
public void saveLockPattern(List<LockPatternView.Cell> pattern, String savedPattern, int userId) {
+ if (!hasSecureLockScreen()) {
+ throw new UnsupportedOperationException(
+ "This operation requires the lock screen feature.");
+ }
if (pattern == null || pattern.size() < MIN_LOCK_PATTERN_SIZE) {
throw new IllegalArgumentException("pattern must not be null and at least "
+ MIN_LOCK_PATTERN_SIZE + " dots long.");
@@ -801,6 +807,10 @@
/** Update the encryption password if it is enabled **/
private void updateEncryptionPassword(final int type, final String password) {
+ if (!hasSecureLockScreen()) {
+ throw new UnsupportedOperationException(
+ "This operation requires the lock screen feature.");
+ }
if (!isDeviceEncryptionEnabled()) {
return;
}
@@ -835,6 +845,10 @@
*/
public void saveLockPassword(String password, String savedPassword, int requestedQuality,
int userHandle) {
+ if (!hasSecureLockScreen()) {
+ throw new UnsupportedOperationException(
+ "This operation requires the lock screen feature.");
+ }
if (password == null || password.length() < MIN_LOCK_PASSWORD_SIZE) {
throw new IllegalArgumentException("password must not be null and at least "
+ "of length " + MIN_LOCK_PASSWORD_SIZE);
@@ -1621,6 +1635,10 @@
*/
public boolean setLockCredentialWithToken(String credential, int type, int requestedQuality,
long tokenHandle, byte[] token, int userId) {
+ if (!hasSecureLockScreen()) {
+ throw new UnsupportedOperationException(
+ "This operation requires the lock screen feature.");
+ }
LockSettingsInternal localService = getLockSettingsInternal();
if (type != CREDENTIAL_TYPE_NONE) {
if (TextUtils.isEmpty(credential) || credential.length() < MIN_LOCK_PASSWORD_SIZE) {
@@ -1854,6 +1872,17 @@
return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0;
}
+ /**
+ * Return true if the device supports the lock screen feature, false otherwise.
+ */
+ public boolean hasSecureLockScreen() {
+ if (mHasSecureLockScreen == null) {
+ mHasSecureLockScreen = Boolean.valueOf(mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN));
+ }
+ return mHasSecureLockScreen.booleanValue();
+ }
+
public static boolean userOwnsFrpCredential(Context context, UserInfo info) {
return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled(context);
}
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 841e5b6..3537465 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -140,6 +140,10 @@
// without throttling, as read from the configuration files.
final ArraySet<String> mAllowUnthrottledLocation = new ArraySet<>();
+ // These are the packages that are white-listed to be able to retrieve location even when user
+ // location settings are off, for emergency purposes, as read from the configuration files.
+ final ArraySet<String> mAllowIgnoreLocationSettings = new ArraySet<>();
+
// These are the action strings of broadcasts which are whitelisted to
// be delivered anonymously even to apps which target O+.
final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>();
@@ -255,6 +259,10 @@
return mAllowUnthrottledLocation;
}
+ public ArraySet<String> getAllowIgnoreLocationSettings() {
+ return mAllowIgnoreLocationSettings;
+ }
+
public ArraySet<String> getLinkedApps() {
return mLinkedApps;
}
@@ -682,6 +690,20 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "allow-ignore-location-settings": {
+ if (allowAll) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ mAllowIgnoreLocationSettings.add(pkgname);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
case "allow-implicit-broadcast": {
if (allowAll) {
String action = parser.getAttributeValue(null, "action");
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 5be70ef..be12700 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -61,8 +61,11 @@
"android_database_SQLiteConnection.cpp",
"android_database_SQLiteGlobal.cpp",
"android_database_SQLiteDebug.cpp",
+ "android_graphics_Canvas.cpp",
+ "android_graphics_ColorSpace.cpp",
"android_graphics_drawable_AnimatedVectorDrawable.cpp",
"android_graphics_drawable_VectorDrawable.cpp",
+ "android_graphics_Picture.cpp",
"android_view_DisplayEventReceiver.cpp",
"android_view_DisplayListCanvas.cpp",
"android_view_TextureLayer.cpp",
@@ -117,8 +120,6 @@
"android_util_StringBlock.cpp",
"android_util_XmlBlock.cpp",
"android_util_jar_StrictJarFile.cpp",
- "android_graphics_Canvas.cpp",
- "android_graphics_Picture.cpp",
"android/graphics/AnimatedImageDrawable.cpp",
"android/graphics/Bitmap.cpp",
"android/graphics/BitmapFactory.cpp",
@@ -185,6 +186,7 @@
"android_hardware_UsbDevice.cpp",
"android_hardware_UsbDeviceConnection.cpp",
"android_hardware_UsbRequest.cpp",
+ "android_hardware_location_ActivityRecognitionHardware.cpp",
"android_util_FileObserver.cpp",
"android/opengl/poly_clip.cpp", // TODO: .arm
"android/opengl/util.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index a586dc1..f458299 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -101,6 +101,7 @@
extern int register_android_hardware_UsbDevice(JNIEnv *env);
extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
extern int register_android_hardware_UsbRequest(JNIEnv *env);
+extern int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env);
extern int register_android_media_AudioEffectDescriptor(JNIEnv *env);
extern int register_android_media_AudioRecord(JNIEnv *env);
@@ -130,6 +131,7 @@
extern int register_android_graphics_Canvas(JNIEnv* env);
extern int register_android_graphics_CanvasProperty(JNIEnv* env);
extern int register_android_graphics_ColorFilter(JNIEnv* env);
+extern int register_android_graphics_ColorSpace(JNIEnv* env);
extern int register_android_graphics_DrawFilter(JNIEnv* env);
extern int register_android_graphics_FontFamily(JNIEnv* env);
extern int register_android_graphics_Matrix(JNIEnv* env);
@@ -150,6 +152,8 @@
extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
extern int register_android_view_DisplayEventReceiver(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
+extern int register_android_view_InputApplicationHandle(JNIEnv* env);
+extern int register_android_view_InputWindowHandle(JNIEnv* env);
extern int register_android_view_TextureLayer(JNIEnv* env);
extern int register_android_view_RenderNode(JNIEnv* env);
extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
@@ -1360,11 +1364,16 @@
REG_JNI(register_android_os_VintfRuntimeInfo),
REG_JNI(register_android_nio_utils),
REG_JNI(register_android_graphics_Canvas),
+ // This needs to be before register_android_graphics_Graphics, or the latter
+ // will not be able to find the jmethodID for ColorSpace.get().
+ REG_JNI(register_android_graphics_ColorSpace),
REG_JNI(register_android_graphics_Graphics),
REG_JNI(register_android_view_DisplayEventReceiver),
REG_JNI(register_android_view_RenderNode),
REG_JNI(register_android_view_RenderNodeAnimator),
REG_JNI(register_android_view_DisplayListCanvas),
+ REG_JNI(register_android_view_InputApplicationHandle),
+ REG_JNI(register_android_view_InputWindowHandle),
REG_JNI(register_android_view_TextureLayer),
REG_JNI(register_android_view_ThreadedRenderer),
REG_JNI(register_android_view_Surface),
@@ -1456,6 +1465,7 @@
REG_JNI(register_android_hardware_UsbDevice),
REG_JNI(register_android_hardware_UsbDeviceConnection),
REG_JNI(register_android_hardware_UsbRequest),
+ REG_JNI(register_android_hardware_location_ActivityRecognitionHardware),
REG_JNI(register_android_media_AudioEffectDescriptor),
REG_JNI(register_android_media_AudioSystem),
REG_JNI(register_android_media_AudioRecord),
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 876bd4f..ad51c47 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -334,7 +334,7 @@
static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
jint offset, jint stride, jint width, jint height,
jint configHandle, jboolean isMutable,
- jfloatArray xyzD50, jobject transferParameters) {
+ jlong colorSpacePtr) {
SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle);
if (NULL != jColors) {
size_t n = env->GetArrayLength(jColors);
@@ -350,17 +350,8 @@
}
SkBitmap bitmap;
- sk_sp<SkColorSpace> colorSpace;
-
- if (xyzD50 == nullptr || transferParameters == nullptr) {
- colorSpace = SkColorSpace::MakeSRGB();
- } else {
- skcms_TransferFunction p = GraphicsJNI::getNativeTransferParameters(env, transferParameters);
- skcms_Matrix3x3 xyzMatrix = GraphicsJNI::getNativeXYZMatrix(env, xyzD50);
- colorSpace = SkColorSpace::MakeRGB(p, xyzMatrix);
- }
-
- bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType, colorSpace));
+ bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType,
+ GraphicsJNI::getNativeColorSpace(colorSpacePtr)));
sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap);
if (!nativeBitmap) {
@@ -582,17 +573,14 @@
bitmapErase(skBitmap, SkColor4f::FromColor(color), SkColorSpace::MakeSRGB());
}
-static void Bitmap_eraseLong(JNIEnv* env, jobject, jlong bitmapHandle, jobject jColorSpace,
- jfloat r, jfloat g, jfloat b, jfloat a) {
- sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(env, jColorSpace);
- if (GraphicsJNI::hasException(env)) {
- return;
- }
-
+static void Bitmap_eraseLong(JNIEnv* env, jobject, jlong bitmapHandle,
+ jlong colorSpaceHandle, jlong colorLong) {
LocalScopedBitmap bitmap(bitmapHandle);
SkBitmap skBitmap;
bitmap->getSkBitmap(&skBitmap);
- SkColor4f color = SkColor4f{r, g, b, a};
+
+ SkColor4f color = GraphicsJNI::convertColorLong(colorLong);
+ sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
bitmapErase(skBitmap, color, cs);
}
@@ -1141,14 +1129,12 @@
}
static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject hardwareBuffer,
- jfloatArray xyzD50, jobject transferParameters) {
- skcms_TransferFunction p = GraphicsJNI::getNativeTransferParameters(env, transferParameters);
- skcms_Matrix3x3 xyzMatrix = GraphicsJNI::getNativeXYZMatrix(env, xyzD50);
- sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeRGB(p, xyzMatrix);
+ jlong colorSpacePtr) {
AHardwareBuffer* hwBuf = android_hardware_HardwareBuffer_getNativeHardwareBuffer(env,
hardwareBuffer);
sp<GraphicBuffer> buffer(AHardwareBuffer_to_GraphicBuffer(hwBuf));
- sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, colorSpace);
+ sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer,
+ GraphicsJNI::getNativeColorSpace(colorSpacePtr));
if (!bitmap.get()) {
ALOGW("failed to create hardware bitmap from hardware buffer");
return NULL;
@@ -1190,7 +1176,7 @@
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gBitmapMethods[] = {
- { "nativeCreate", "([IIIIIIZ[FLandroid/graphics/ColorSpace$Rgb$TransferParameters;)Landroid/graphics/Bitmap;",
+ { "nativeCreate", "([IIIIIIZJ)Landroid/graphics/Bitmap;",
(void*)Bitmap_creator },
{ "nativeCopy", "(JIZ)Landroid/graphics/Bitmap;",
(void*)Bitmap_copy },
@@ -1204,7 +1190,7 @@
{ "nativeCompress", "(JIILjava/io/OutputStream;[B)Z",
(void*)Bitmap_compress },
{ "nativeErase", "(JI)V", (void*)Bitmap_erase },
- { "nativeErase", "(JLandroid/graphics/ColorSpace;FFFF)V", (void*)Bitmap_eraseLong },
+ { "nativeErase", "(JJJ)V", (void*)Bitmap_eraseLong },
{ "nativeRowBytes", "(J)I", (void*)Bitmap_rowBytes },
{ "nativeConfig", "(J)I", (void*)Bitmap_config },
{ "nativeHasAlpha", "(J)Z", (void*)Bitmap_hasAlpha },
@@ -1236,7 +1222,7 @@
(void*)Bitmap_copyPreserveInternalConfig },
{ "nativeCreateHardwareBitmap", "(Landroid/graphics/GraphicBuffer;)Landroid/graphics/Bitmap;",
(void*) Bitmap_createHardwareBitmap },
- { "nativeWrapHardwareBufferBitmap", "(Landroid/hardware/HardwareBuffer;[FLandroid/graphics/ColorSpace$Rgb$TransferParameters;)Landroid/graphics/Bitmap;",
+ { "nativeWrapHardwareBufferBitmap", "(Landroid/hardware/HardwareBuffer;J)Landroid/graphics/Bitmap;",
(void*) Bitmap_wrapHardwareBufferBitmap },
{ "nativeCreateGraphicBufferHandle", "(J)Landroid/graphics/GraphicBuffer;",
(void*) Bitmap_createGraphicBufferHandle },
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 4b0ab5b..7d0d3d8 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -179,7 +179,7 @@
}
static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
- jobject padding, jobject options) {
+ jobject padding, jobject options, jlong colorSpaceHandle) {
// Set default values for the options parameters.
int sampleSize = 1;
bool onlyDecodeSize = false;
@@ -189,7 +189,7 @@
float scale = 1.0f;
bool requireUnpremultiplied = false;
jobject javaBitmap = NULL;
- sk_sp<SkColorSpace> prefColorSpace = nullptr;
+ sk_sp<SkColorSpace> prefColorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
// Update with options supplied by the client.
if (options != NULL) {
@@ -213,8 +213,6 @@
jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
- jobject jcolorSpace = env->GetObjectField(options, gOptions_colorSpaceFieldID);
- prefColorSpace = GraphicsJNI::getNativeColorSpace(env, jcolorSpace);
isHardware = GraphicsJNI::isHardwareConfig(env, jconfig);
isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
@@ -515,7 +513,7 @@
}
static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
- jobject padding, jobject options) {
+ jobject padding, jobject options, jlong colorSpaceHandle) {
jobject bitmap = NULL;
std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage));
@@ -524,13 +522,13 @@
std::unique_ptr<SkStreamRewindable> bufferedStream(
SkFrontBufferedStream::Make(std::move(stream), SkCodec::MinBufferedBytesNeeded()));
SkASSERT(bufferedStream.get() != NULL);
- bitmap = doDecode(env, std::move(bufferedStream), padding, options);
+ bitmap = doDecode(env, std::move(bufferedStream), padding, options, colorSpaceHandle);
}
return bitmap;
}
static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor,
- jobject padding, jobject bitmapFactoryOptions) {
+ jobject padding, jobject bitmapFactoryOptions, jlong colorSpaceHandle) {
NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
@@ -566,7 +564,8 @@
// If there is no offset for the file descriptor, we use SkFILEStream directly.
if (::lseek(descriptor, 0, SEEK_CUR) == 0) {
assert(isSeekable(dupDescriptor));
- return doDecode(env, std::move(fileStream), padding, bitmapFactoryOptions);
+ return doDecode(env, std::move(fileStream), padding, bitmapFactoryOptions,
+ colorSpaceHandle);
}
// Use a buffered stream. Although an SkFILEStream can be rewound, this
@@ -575,24 +574,25 @@
std::unique_ptr<SkStreamRewindable> stream(SkFrontBufferedStream::Make(std::move(fileStream),
SkCodec::MinBufferedBytesNeeded()));
- return doDecode(env, std::move(stream), padding, bitmapFactoryOptions);
+ return doDecode(env, std::move(stream), padding, bitmapFactoryOptions, colorSpaceHandle);
}
static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jlong native_asset,
- jobject padding, jobject options) {
+ jobject padding, jobject options, jlong colorSpaceHandle) {
Asset* asset = reinterpret_cast<Asset*>(native_asset);
// since we know we'll be done with the asset when we return, we can
// just use a simple wrapper
- return doDecode(env, skstd::make_unique<AssetStreamAdaptor>(asset), padding, options);
+ return doDecode(env, skstd::make_unique<AssetStreamAdaptor>(asset), padding, options,
+ colorSpaceHandle);
}
static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
- jint offset, jint length, jobject options) {
+ jint offset, jint length, jobject options, jlong colorSpaceHandle) {
AutoJavaByteArray ar(env, byteArray);
return doDecode(env, skstd::make_unique<SkMemoryStream>(ar.ptr() + offset, length, false),
- nullptr, options);
+ nullptr, options, colorSpaceHandle);
}
static jboolean nativeIsSeekable(JNIEnv* env, jobject, jobject fileDescriptor) {
@@ -600,31 +600,26 @@
return isSeekable(descriptor) ? JNI_TRUE : JNI_FALSE;
}
-jobject decodeBitmap(JNIEnv* env, void* data, size_t size) {
- return doDecode(env, skstd::make_unique<SkMemoryStream>(data, size),
- nullptr, nullptr);
-}
-
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gMethods[] = {
{ "nativeDecodeStream",
- "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+ "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;J)Landroid/graphics/Bitmap;",
(void*)nativeDecodeStream
},
{ "nativeDecodeFileDescriptor",
- "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+ "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;J)Landroid/graphics/Bitmap;",
(void*)nativeDecodeFileDescriptor
},
{ "nativeDecodeAsset",
- "(JLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+ "(JLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;J)Landroid/graphics/Bitmap;",
(void*)nativeDecodeAsset
},
{ "nativeDecodeByteArray",
- "([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+ "([BIILandroid/graphics/BitmapFactory$Options;J)Landroid/graphics/Bitmap;",
(void*)nativeDecodeByteArray
},
diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h
index 1ee49fa..e37c98d 100644
--- a/core/jni/android/graphics/BitmapFactory.h
+++ b/core/jni/android/graphics/BitmapFactory.h
@@ -28,6 +28,4 @@
jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format);
-jobject decodeBitmap(JNIEnv* env, void* data, size_t size);
-
#endif // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index f831c05..b4ba749 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -124,7 +124,7 @@
* reportSizeToVM not supported
*/
static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint inputX,
- jint inputY, jint inputWidth, jint inputHeight, jobject options) {
+ jint inputY, jint inputWidth, jint inputHeight, jobject options, jlong colorSpaceHandle) {
// Set default options.
int sampleSize = 1;
@@ -132,14 +132,12 @@
bool requireUnpremul = false;
jobject javaBitmap = NULL;
bool isHardware = false;
- sk_sp<SkColorSpace> colorSpace = nullptr;
+ sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
// Update the default options with any options supplied by the client.
if (NULL != options) {
sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
colorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
- jobject jcolorSpace = env->GetObjectField(options, gOptions_colorSpaceFieldID);
- colorSpace = GraphicsJNI::getNativeColorSpace(env, jcolorSpace);
isHardware = GraphicsJNI::isHardwareConfig(env, jconfig);
requireUnpremul = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
@@ -255,7 +253,7 @@
static const JNINativeMethod gBitmapRegionDecoderMethods[] = {
{ "nativeDecodeRegion",
- "(JIIIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+ "(JIIIILandroid/graphics/BitmapFactory$Options;J)Landroid/graphics/Bitmap;",
(void*)nativeDecodeRegion},
{ "nativeGetHeight", "(J)I", (void*)nativeGetHeight},
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 9e74b88..6570992 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -178,23 +178,11 @@
static jmethodID gVMRuntime_newNonMovableArray;
static jmethodID gVMRuntime_addressOf;
-static jfieldID gTransferParams_aFieldID;
-static jfieldID gTransferParams_bFieldID;
-static jfieldID gTransferParams_cFieldID;
-static jfieldID gTransferParams_dFieldID;
-static jfieldID gTransferParams_eFieldID;
-static jfieldID gTransferParams_fFieldID;
-static jfieldID gTransferParams_gFieldID;
-
static jclass gColorSpace_class;
-static jfieldID gColorSpace_IlluminantD50FieldID;
-static jmethodID gColorSpace_adaptMethodID;
static jmethodID gColorSpace_getMethodID;
static jmethodID gColorSpace_matchMethodID;
static jclass gColorSpaceRGB_class;
-static jmethodID gColorSpaceRGB_getTransferParametersMethodID;
-static jmethodID gColorSpaceRGB_getTransformMethodID;
static jmethodID gColorSpaceRGB_constructorMethodID;
static jclass gColorSpace_Named_class;
@@ -424,63 +412,6 @@
///////////////////////////////////////////////////////////////////////////////
-skcms_TransferFunction GraphicsJNI::getNativeTransferParameters(JNIEnv* env, jobject transferParams) {
- skcms_TransferFunction p;
- p.a = (float) env->GetDoubleField(transferParams, gTransferParams_aFieldID);
- p.b = (float) env->GetDoubleField(transferParams, gTransferParams_bFieldID);
- p.c = (float) env->GetDoubleField(transferParams, gTransferParams_cFieldID);
- p.d = (float) env->GetDoubleField(transferParams, gTransferParams_dFieldID);
- p.e = (float) env->GetDoubleField(transferParams, gTransferParams_eFieldID);
- p.f = (float) env->GetDoubleField(transferParams, gTransferParams_fFieldID);
- p.g = (float) env->GetDoubleField(transferParams, gTransferParams_gFieldID);
- return p;
-}
-
-skcms_Matrix3x3 GraphicsJNI::getNativeXYZMatrix(JNIEnv* env, jfloatArray xyzD50) {
- skcms_Matrix3x3 xyzMatrix;
- jfloat* array = env->GetFloatArrayElements(xyzD50, NULL);
- xyzMatrix.vals[0][0] = array[0];
- xyzMatrix.vals[1][0] = array[1];
- xyzMatrix.vals[2][0] = array[2];
- xyzMatrix.vals[0][1] = array[3];
- xyzMatrix.vals[1][1] = array[4];
- xyzMatrix.vals[2][1] = array[5];
- xyzMatrix.vals[0][2] = array[6];
- xyzMatrix.vals[1][2] = array[7];
- xyzMatrix.vals[2][2] = array[8];
- env->ReleaseFloatArrayElements(xyzD50, array, 0);
- return xyzMatrix;
-}
-
-sk_sp<SkColorSpace> GraphicsJNI::getNativeColorSpace(JNIEnv* env, jobject colorSpace) {
- if (colorSpace == nullptr) return nullptr;
- if (!env->IsInstanceOf(colorSpace, gColorSpaceRGB_class)) {
- doThrowIAE(env, "The color space must be an RGB color space");
- return nullptr;
- }
-
- jobject transferParams = env->CallObjectMethod(colorSpace,
- gColorSpaceRGB_getTransferParametersMethodID);
- if (transferParams == nullptr) {
- doThrowIAE(env, "The color space must use an ICC parametric transfer function");
- return nullptr;
- }
-
- jfloatArray illuminantD50 = (jfloatArray) env->GetStaticObjectField(gColorSpace_class,
- gColorSpace_IlluminantD50FieldID);
- jobject colorSpaceD50 = env->CallStaticObjectMethod(gColorSpace_class,
- gColorSpace_adaptMethodID, colorSpace, illuminantD50);
-
- jfloatArray xyzD50 = (jfloatArray) env->CallObjectMethod(colorSpaceD50,
- gColorSpaceRGB_getTransformMethodID);
-
- skcms_Matrix3x3 xyzMatrix = getNativeXYZMatrix(env, xyzD50);
- skcms_TransferFunction transferFunction = getNativeTransferParameters(env, transferParams);
-
- return SkColorSpace::MakeRGB(transferFunction, xyzMatrix);
-}
-
-
jobject GraphicsJNI::getColorSpace(JNIEnv* env, sk_sp<SkColorSpace>& decodeColorSpace,
SkColorType decodeColorType) {
jobject colorSpace = nullptr;
@@ -712,20 +643,7 @@
"(Ljava/lang/Class;I)Ljava/lang/Object;");
gVMRuntime_addressOf = GetMethodIDOrDie(env, gVMRuntime_class, "addressOf", "(Ljava/lang/Object;)J");
- jclass transfer_params_class = FindClassOrDie(env, "android/graphics/ColorSpace$Rgb$TransferParameters");
- gTransferParams_aFieldID = GetFieldIDOrDie(env, transfer_params_class, "a", "D");
- gTransferParams_bFieldID = GetFieldIDOrDie(env, transfer_params_class, "b", "D");
- gTransferParams_cFieldID = GetFieldIDOrDie(env, transfer_params_class, "c", "D");
- gTransferParams_dFieldID = GetFieldIDOrDie(env, transfer_params_class, "d", "D");
- gTransferParams_eFieldID = GetFieldIDOrDie(env, transfer_params_class, "e", "D");
- gTransferParams_fFieldID = GetFieldIDOrDie(env, transfer_params_class, "f", "D");
- gTransferParams_gFieldID = GetFieldIDOrDie(env, transfer_params_class, "g", "D");
-
gColorSpace_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ColorSpace"));
- gColorSpace_IlluminantD50FieldID = GetStaticFieldIDOrDie(env,
- gColorSpace_class, "ILLUMINANT_D50", "[F");
- gColorSpace_adaptMethodID = GetStaticMethodIDOrDie(env, gColorSpace_class, "adapt",
- "(Landroid/graphics/ColorSpace;[F)Landroid/graphics/ColorSpace;");
gColorSpace_getMethodID = GetStaticMethodIDOrDie(env, gColorSpace_class,
"get", "(Landroid/graphics/ColorSpace$Named;)Landroid/graphics/ColorSpace;");
gColorSpace_matchMethodID = GetStaticMethodIDOrDie(env, gColorSpace_class, "match",
@@ -735,10 +653,6 @@
FindClassOrDie(env, "android/graphics/ColorSpace$Rgb"));
gColorSpaceRGB_constructorMethodID = GetMethodIDOrDie(env, gColorSpaceRGB_class,
"<init>", "(Ljava/lang/String;[FLandroid/graphics/ColorSpace$Rgb$TransferParameters;)V");
- gColorSpaceRGB_getTransferParametersMethodID = GetMethodIDOrDie(env, gColorSpaceRGB_class,
- "getTransferParameters", "()Landroid/graphics/ColorSpace$Rgb$TransferParameters;");
- gColorSpaceRGB_getTransformMethodID = GetMethodIDOrDie(env, gColorSpaceRGB_class,
- "getTransform", "()[F");
gColorSpace_Named_class = MakeGlobalRefOrDie(env,
FindClassOrDie(env, "android/graphics/ColorSpace$Named"));
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index 699d153..dc0d022 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -100,12 +100,27 @@
int srcStride, int x, int y, int width, int height,
SkBitmap* dstBitmap);
- static skcms_TransferFunction getNativeTransferParameters(JNIEnv* env, jobject transferParams);
- static skcms_Matrix3x3 getNativeXYZMatrix(JNIEnv* env, jfloatArray xyzD50);
- static sk_sp<SkColorSpace> getNativeColorSpace(JNIEnv* env, jobject colorSpace);
+ /**
+ * Convert the native SkColorSpace retrieved from ColorSpace.Rgb.getNativeInstance().
+ *
+ * This will never throw an Exception. If the ColorSpace is one that Skia cannot
+ * use, ColorSpace.Rgb.getNativeInstance() would have thrown an Exception. It may,
+ * however, be nullptr, which may be acceptable.
+ */
+ static sk_sp<SkColorSpace> getNativeColorSpace(jlong colorSpaceHandle);
static jobject getColorSpace(JNIEnv* env, sk_sp<SkColorSpace>& decodeColorSpace,
SkColorType decodeColorType);
+
+ /**
+ * Convert from a Java @ColorLong to an SkColor4f that Skia can use directly.
+ *
+ * This ignores the encoded ColorSpace, besides checking to see if it is sRGB,
+ * which is encoded differently. The color space should be passed down separately
+ * via ColorSpace#getNativeInstance(), and converted with getNativeColorSpace(),
+ * above.
+ */
+ static SkColor4f convertColorLong(jlong color);
};
class HeapAllocator : public SkBRDAllocator {
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index df735ae..5f12665 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -202,7 +202,7 @@
jint desiredWidth, jint desiredHeight, jobject jsubset,
jboolean requireMutable, jint allocator,
jboolean requireUnpremul, jboolean preferRamOverQuality,
- jboolean asAlphaMask, jobject jcolorSpace) {
+ jboolean asAlphaMask, jlong colorSpaceHandle) {
auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
SkAndroidCodec* codec = decoder->mCodec.get();
const SkISize desiredSize = SkISize::Make(desiredWidth, desiredHeight);
@@ -256,7 +256,7 @@
// This is currently the only way to know that we should decode to F16.
colorType = codec->computeOutputColorType(colorType);
}
- sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(env, jcolorSpace);
+ sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
colorSpace = codec->computeOutputColorSpace(colorType, colorSpace);
decodeInfo = decodeInfo.makeColorType(colorType).makeColorSpace(colorSpace);
@@ -508,7 +508,7 @@
{ "nCreate", "([BIILandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray },
{ "nCreate", "(Ljava/io/InputStream;[BLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateInputStream },
{ "nCreate", "(Ljava/io/FileDescriptor;Landroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateFd },
- { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;ZIILandroid/graphics/Rect;ZIZZZLandroid/graphics/ColorSpace;)Landroid/graphics/Bitmap;",
+ { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;ZIILandroid/graphics/Rect;ZIZZZJ)Landroid/graphics/Bitmap;",
(void*) ImageDecoder_nDecodeBitmap },
{ "nGetSampledSize","(JI)Landroid/util/Size;", (void*) ImageDecoder_nGetSampledSize },
{ "nGetPadding", "(JLandroid/graphics/Rect;)V", (void*) ImageDecoder_nGetPadding },
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index e2e3042..7679c5b 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -557,41 +557,6 @@
return result;
}
- // FIXME: Make this CriticalNative when we no longer need to use JNIEnv. b/122514935 will allow
- // passing the SkColorSpace directly from JNI.
- static void setColor(JNIEnv* env, jobject clazz, jlong paintHandle, jobject jColorSpace,
- jfloat r, jfloat g, jfloat b, jfloat a) {
- sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(env, jColorSpace);
- if (GraphicsJNI::hasException(env)) {
- return;
- }
-
- SkColor4f color = SkColor4f{r, g, b, a};
- reinterpret_cast<Paint*>(paintHandle)->setColor4f(color, cs.get());
- }
-
- // FIXME: Make this CriticalNative when we no longer need to use JNIEnv. b/122514935 will allow
- // passing the SkColorSpace directly from JNI.
- static void setShadowLayer(JNIEnv* env, jobject clazz, jlong paintHandle, jfloat radius,
- jfloat dx, jfloat dy, jobject jColorSpace,
- jfloat r, jfloat g, jfloat b, jfloat a) {
- sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(env, jColorSpace);
- if (GraphicsJNI::hasException(env)) {
- return;
- }
-
- SkColor4f color = SkColor4f{r, g, b, a};
-
- Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- if (radius <= 0) {
- paint->setLooper(nullptr);
- }
- else {
- SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius);
- paint->setLooper(SkBlurDrawLooper::Make(color, cs.get(), sigma, dx, dy));
- }
- }
-
// ------------------ @FastNative ---------------------------
static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
@@ -787,6 +752,13 @@
obj->setStyle(style);
}
+ static void setColor(jlong paintHandle, jlong colorSpaceHandle,
+ jfloat r, jfloat g, jfloat b, jfloat a) {
+ sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
+ SkColor4f color = SkColor4f{r, g, b, a};
+ reinterpret_cast<Paint*>(paintHandle)->setColor4f(color, cs.get());
+ }
+
static void setAlpha(jlong paintHandle, jint a) {
reinterpret_cast<Paint*>(paintHandle)->setAlpha(a);
}
@@ -1034,6 +1006,22 @@
return SkScalarToFloat(Paint::kStdStrikeThru_Thickness * textSize);
}
+ static void setShadowLayer(jlong paintHandle, jfloat radius,
+ jfloat dx, jfloat dy, jlong colorSpaceHandle,
+ jfloat r, jfloat g, jfloat b, jfloat a) {
+ sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
+ SkColor4f color = SkColor4f{r, g, b, a};
+
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ if (radius <= 0) {
+ paint->setLooper(nullptr);
+ }
+ else {
+ SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius);
+ paint->setLooper(SkBlurDrawLooper::Make(color, cs.get(), sigma, dx, dy));
+ }
+ }
+
static jboolean hasShadowLayer(jlong paintHandle) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
return paint->getLooper() && paint->getLooper()->asABlurShadow(nullptr);
@@ -1082,9 +1070,6 @@
{"nGetRunAdvance", "(J[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F},
{"nGetOffsetForAdvance", "(J[CIIIIZF)I",
(void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I},
- {"nSetColor","(JLandroid/graphics/ColorSpace;FFFF)V", (void*) PaintGlue::setColor},
- {"nSetShadowLayer", "(JFFFLandroid/graphics/ColorSpace;FFFF)V",
- (void*)PaintGlue::setShadowLayer},
// --------------- @FastNative ----------------------
@@ -1114,6 +1099,7 @@
{"nSetDither","(JZ)V", (void*) PaintGlue::setDither},
{"nGetStyle","(J)I", (void*) PaintGlue::getStyle},
{"nSetStyle","(JI)V", (void*) PaintGlue::setStyle},
+ {"nSetColor","(JJFFFF)V", (void*) PaintGlue::setColor},
{"nSetAlpha","(JI)V", (void*) PaintGlue::setAlpha},
{"nGetStrokeWidth","(J)F", (void*) PaintGlue::getStrokeWidth},
{"nSetStrokeWidth","(JF)V", (void*) PaintGlue::setStrokeWidth},
@@ -1154,6 +1140,7 @@
{"nGetUnderlineThickness","(J)F", (void*) PaintGlue::getUnderlineThickness},
{"nGetStrikeThruPosition","(J)F", (void*) PaintGlue::getStrikeThruPosition},
{"nGetStrikeThruThickness","(J)F", (void*) PaintGlue::getStrikeThruThickness},
+ {"nSetShadowLayer", "(JFFFJFFFF)V", (void*)PaintGlue::setShadowLayer},
{"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer},
{"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement},
};
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index fd1d87f..d29857d 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -37,6 +37,12 @@
}
}
+Picture::Picture(sk_sp<SkPicture>&& src) {
+ mPicture = std::move(src);
+ mWidth = 0;
+ mHeight = 0;
+}
+
Canvas* Picture::beginRecording(int width, int height) {
mPicture.reset(NULL);
mRecorder.reset(new SkPictureRecorder);
diff --git a/core/jni/android/graphics/Picture.h b/core/jni/android/graphics/Picture.h
index 3068631..536f651 100644
--- a/core/jni/android/graphics/Picture.h
+++ b/core/jni/android/graphics/Picture.h
@@ -37,6 +37,7 @@
class Picture {
public:
explicit Picture(const Picture* src = NULL);
+ explicit Picture(sk_sp<SkPicture>&& src);
Canvas* beginRecording(int width, int height);
diff --git a/core/jni/android_app_ActivityThread.cpp b/core/jni/android_app_ActivityThread.cpp
index d56e4c5..93f2525 100644
--- a/core/jni/android_app_ActivityThread.cpp
+++ b/core/jni/android_app_ActivityThread.cpp
@@ -24,6 +24,8 @@
#include "core_jni_helpers.h"
#include <unistd.h>
+#include <bionic_malloc.h>
+
namespace android {
static void android_app_ActivityThread_purgePendingResources(JNIEnv* env, jobject clazz) {
@@ -38,13 +40,18 @@
minikin::Layout::dumpMinikinStats(fd);
}
+static void android_app_ActivityThread_initZygoteChildHeapProfiling(JNIEnv* env, jobject clazz) {
+ android_mallopt(M_INIT_ZYGOTE_CHILD_PROFILING, nullptr, 0);
+}
static JNINativeMethod gActivityThreadMethods[] = {
// ------------ Regular JNI ------------------
{ "nPurgePendingResources", "()V",
(void*) android_app_ActivityThread_purgePendingResources },
{ "nDumpGraphicsInfo", "(Ljava/io/FileDescriptor;)V",
- (void*) android_app_ActivityThread_dumpGraphics }
+ (void*) android_app_ActivityThread_dumpGraphics },
+ { "nInitZygoteChildHeapProfiling", "()V",
+ (void*) android_app_ActivityThread_initZygoteChildHeapProfiling }
};
int register_android_app_ActivityThread(JNIEnv* env) {
diff --git a/core/jni/android_graphics_ColorSpace.cpp b/core/jni/android_graphics_ColorSpace.cpp
new file mode 100644
index 0000000..7648fd0
--- /dev/null
+++ b/core/jni/android_graphics_ColorSpace.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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 "jni.h"
+#include "GraphicsJNI.h"
+#include "core_jni_helpers.h"
+
+#include "SkColor.h"
+#include "SkColorSpace.h"
+
+using namespace android;
+
+static skcms_Matrix3x3 getNativeXYZMatrix(JNIEnv* env, jfloatArray xyzD50) {
+ skcms_Matrix3x3 xyzMatrix;
+ jfloat* array = env->GetFloatArrayElements(xyzD50, NULL);
+ xyzMatrix.vals[0][0] = array[0];
+ xyzMatrix.vals[1][0] = array[1];
+ xyzMatrix.vals[2][0] = array[2];
+ xyzMatrix.vals[0][1] = array[3];
+ xyzMatrix.vals[1][1] = array[4];
+ xyzMatrix.vals[2][1] = array[5];
+ xyzMatrix.vals[0][2] = array[6];
+ xyzMatrix.vals[1][2] = array[7];
+ xyzMatrix.vals[2][2] = array[8];
+ env->ReleaseFloatArrayElements(xyzD50, array, 0);
+ return xyzMatrix;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static float halfToFloat(uint16_t bits) {
+ __fp16 h;
+ memcpy(&h, &bits, 2);
+ return (float)h;
+}
+
+SkColor4f GraphicsJNI::convertColorLong(jlong color) {
+ if ((color & 0x3f) == 0) {
+ // This corresponds to sRGB, which is treated differently than the rest.
+ uint8_t a = color >> 56 & 0xff;
+ uint8_t r = color >> 48 & 0xff;
+ uint8_t g = color >> 40 & 0xff;
+ uint8_t b = color >> 32 & 0xff;
+ SkColor c = SkColorSetARGB(a, r, g, b);
+ return SkColor4f::FromColor(c);
+ }
+
+ // These match the implementation of android.graphics.Color#red(long) etc.
+ float r = halfToFloat((uint16_t)(color >> 48 & 0xffff));
+ float g = halfToFloat((uint16_t)(color >> 32 & 0xffff));
+ float b = halfToFloat((uint16_t)(color >> 16 & 0xffff));
+ float a = (color >> 6 & 0x3ff) / 1023.0f;
+
+ return SkColor4f{r, g, b, a};
+}
+
+sk_sp<SkColorSpace> GraphicsJNI::getNativeColorSpace(jlong colorSpaceHandle) {
+ if (colorSpaceHandle == 0) return nullptr;
+ return sk_ref_sp(reinterpret_cast<SkColorSpace*>(colorSpaceHandle));
+}
+
+static void unref_colorSpace(SkColorSpace* cs) {
+ SkSafeUnref(cs);
+}
+
+static jlong ColorSpace_getNativeFinalizer(JNIEnv*, jobject) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&unref_colorSpace));
+}
+
+static jlong ColorSpace_creator(JNIEnv* env, jobject, jfloat a, jfloat b, jfloat c,
+ jfloat d, jfloat e, jfloat f, jfloat g, jfloatArray xyzD50) {
+ skcms_TransferFunction p;
+ p.a = a;
+ p.b = b;
+ p.c = c;
+ p.d = d;
+ p.e = e;
+ p.f = f;
+ p.g = g;
+ skcms_Matrix3x3 xyzMatrix = getNativeXYZMatrix(env, xyzD50);
+
+ return reinterpret_cast<jlong>(SkColorSpace::MakeRGB(p, xyzMatrix).release());
+}
+
+static const JNINativeMethod gColorSpaceRgbMethods[] = {
+ { "nativeGetNativeFinalizer", "()J", (void*)ColorSpace_getNativeFinalizer },
+ { "nativeCreate", "(FFFFFFF[F)J", (void*)ColorSpace_creator }
+};
+
+namespace android {
+
+int register_android_graphics_ColorSpace(JNIEnv* env) {
+ return android::RegisterMethodsOrDie(env, "android/graphics/ColorSpace$Rgb",
+ gColorSpaceRgbMethods, NELEM(gColorSpaceRgbMethods));
+}
+
+}; // namespace android
diff --git a/core/jni/android_hardware_input_InputApplicationHandle.cpp b/core/jni/android_hardware_input_InputApplicationHandle.cpp
index 10005dd..1ab5843 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.cpp
+++ b/core/jni/android_hardware_input_InputApplicationHandle.cpp
@@ -93,7 +93,7 @@
// --- Global functions ---
-sp<InputApplicationHandle> android_server_InputApplicationHandle_getHandle(
+sp<InputApplicationHandle> android_view_InputApplicationHandle_getHandle(
JNIEnv* env, jobject inputApplicationHandleObj) {
if (!inputApplicationHandleObj) {
return NULL;
@@ -108,7 +108,7 @@
} else {
jweak objWeak = env->NewWeakGlobalRef(inputApplicationHandleObj);
handle = new NativeInputApplicationHandle(objWeak);
- handle->incStrong((void*)android_server_InputApplicationHandle_getHandle);
+ handle->incStrong((void*)android_view_InputApplicationHandle_getHandle);
env->SetLongField(inputApplicationHandleObj, gInputApplicationHandleClassInfo.ptr,
reinterpret_cast<jlong>(handle));
}
@@ -118,7 +118,7 @@
// --- JNI ---
-static void android_server_InputApplicationHandle_nativeDispose(JNIEnv* env, jobject obj) {
+static void android_view_InputApplicationHandle_nativeDispose(JNIEnv* env, jobject obj) {
AutoMutex _l(gHandleMutex);
jlong ptr = env->GetLongField(obj, gInputApplicationHandleClassInfo.ptr);
@@ -126,7 +126,7 @@
env->SetLongField(obj, gInputApplicationHandleClassInfo.ptr, 0);
NativeInputApplicationHandle* handle = reinterpret_cast<NativeInputApplicationHandle*>(ptr);
- handle->decStrong((void*)android_server_InputApplicationHandle_getHandle);
+ handle->decStrong((void*)android_view_InputApplicationHandle_getHandle);
}
}
@@ -134,7 +134,7 @@
static const JNINativeMethod gInputApplicationHandleMethods[] = {
/* name, signature, funcPtr */
{ "nativeDispose", "()V",
- (void*) android_server_InputApplicationHandle_nativeDispose },
+ (void*) android_view_InputApplicationHandle_nativeDispose },
};
#define FIND_CLASS(var, className) \
@@ -145,7 +145,7 @@
var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
-int register_android_server_InputApplicationHandle(JNIEnv* env) {
+int register_android_view_InputApplicationHandle(JNIEnv* env) {
int res = jniRegisterNativeMethods(env, "android/view/InputApplicationHandle",
gInputApplicationHandleMethods, NELEM(gInputApplicationHandleMethods));
(void) res; // Faked use when LOG_NDEBUG.
diff --git a/core/jni/android_hardware_input_InputApplicationHandle.h b/core/jni/android_hardware_input_InputApplicationHandle.h
index 7115611..5abeab4 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.h
+++ b/core/jni/android_hardware_input_InputApplicationHandle.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H
-#define _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H
+#ifndef _ANDROID_VIEW_INPUT_APPLICATION_HANDLE_H
+#define _ANDROID_VIEW_INPUT_APPLICATION_HANDLE_H
#include <string>
@@ -40,9 +40,9 @@
};
-extern sp<InputApplicationHandle> android_server_InputApplicationHandle_getHandle(
+extern sp<InputApplicationHandle> android_view_InputApplicationHandle_getHandle(
JNIEnv* env, jobject inputApplicationHandleObj);
} // namespace android
-#endif // _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H
+#endif // _ANDROID_VIEW_INPUT_APPLICATION_HANDLE_H
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 76920f5..c0e45b1 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -159,7 +159,7 @@
gInputWindowHandleClassInfo.inputApplicationHandle);
if (inputApplicationHandleObj) {
sp<InputApplicationHandle> inputApplicationHandle =
- android_server_InputApplicationHandle_getHandle(env, inputApplicationHandleObj);
+ android_view_InputApplicationHandle_getHandle(env, inputApplicationHandleObj);
if (inputApplicationHandle != nullptr) {
inputApplicationHandle->updateInfo();
mInfo.applicationInfo = *(inputApplicationHandle->getInfo());
@@ -174,7 +174,7 @@
// --- Global functions ---
-sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle(
+sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
JNIEnv* env, jobject inputWindowHandleObj) {
if (!inputWindowHandleObj) {
return NULL;
@@ -189,7 +189,7 @@
} else {
jweak objWeak = env->NewWeakGlobalRef(inputWindowHandleObj);
handle = new NativeInputWindowHandle(objWeak);
- handle->incStrong((void*)android_server_InputWindowHandle_getHandle);
+ handle->incStrong((void*)android_view_InputWindowHandle_getHandle);
env->SetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr,
reinterpret_cast<jlong>(handle));
}
@@ -199,7 +199,7 @@
// --- JNI ---
-static void android_server_InputWindowHandle_nativeDispose(JNIEnv* env, jobject obj) {
+static void android_view_InputWindowHandle_nativeDispose(JNIEnv* env, jobject obj) {
AutoMutex _l(gHandleMutex);
jlong ptr = env->GetLongField(obj, gInputWindowHandleClassInfo.ptr);
@@ -207,7 +207,7 @@
env->SetLongField(obj, gInputWindowHandleClassInfo.ptr, 0);
NativeInputWindowHandle* handle = reinterpret_cast<NativeInputWindowHandle*>(ptr);
- handle->decStrong((void*)android_server_InputWindowHandle_getHandle);
+ handle->decStrong((void*)android_view_InputWindowHandle_getHandle);
}
}
@@ -215,7 +215,7 @@
static const JNINativeMethod gInputWindowHandleMethods[] = {
/* name, signature, funcPtr */
{ "nativeDispose", "()V",
- (void*) android_server_InputWindowHandle_nativeDispose },
+ (void*) android_view_InputWindowHandle_nativeDispose },
};
#define FIND_CLASS(var, className) \
@@ -226,7 +226,7 @@
var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
-int register_android_server_InputWindowHandle(JNIEnv* env) {
+int register_android_view_InputWindowHandle(JNIEnv* env) {
int res = jniRegisterNativeMethods(env, "android/view/InputWindowHandle",
gInputWindowHandleMethods, NELEM(gInputWindowHandleMethods));
(void) res; // Faked use when LOG_NDEBUG.
diff --git a/core/jni/android_hardware_input_InputWindowHandle.h b/core/jni/android_hardware_input_InputWindowHandle.h
index 54b89f5..de5bd6e 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.h
+++ b/core/jni/android_hardware_input_InputWindowHandle.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H
-#define _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H
+#ifndef _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H
+#define _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H
#include <input/InputWindow.h>
@@ -38,9 +38,9 @@
};
-extern sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle(
+extern sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
JNIEnv* env, jobject inputWindowHandleObj);
} // namespace android
-#endif // _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H
+#endif // _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H
diff --git a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
new file mode 100644
index 0000000..1c9ab94
--- /dev/null
+++ b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2014, 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.
+ */
+
+#define LOG_TAG "ActivityRecognitionHardware"
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+
+// #include <hardware/activity_recognition.h>
+// The activity recognition HAL is being deprecated. This means -
+// i) Android framework code shall not depend on activity recognition
+// being provided through the activity_recognition.h interface.
+// ii) activity recognition HAL will not be binderized as the other HALs.
+//
+
+/**
+ * Initializes the ActivityRecognitionHardware class from the native side.
+ */
+static void class_init(JNIEnv* /*env*/, jclass /*clazz*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+}
+
+/**
+ * Initializes and connect the callbacks handlers in the HAL.
+ */
+static void initialize(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+}
+
+/**
+ * De-initializes the ActivityRecognitionHardware from the native side.
+ */
+static void release(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+}
+
+/**
+ * Returns true if ActivityRecognition HAL is supported, false otherwise.
+ */
+static jboolean is_supported(JNIEnv* /*env*/, jclass /*clazz*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return JNI_FALSE;
+}
+
+/**
+ * Gets an array representing the supported activities.
+ */
+static jobjectArray get_supported_activities(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return NULL;
+}
+
+/**
+ * Enables a given activity event to be actively monitored.
+ */
+static int enable_activity_event(
+ JNIEnv* /*env*/,
+ jobject /*obj*/,
+ jint /*activity_handle*/,
+ jint /*event_type*/,
+ jlong /*report_latency_ns*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return android::NO_INIT;
+}
+
+/**
+ * Disables a given activity event from being actively monitored.
+ */
+static int disable_activity_event(
+ JNIEnv* /*env*/,
+ jobject /*obj*/,
+ jint /*activity_handle*/,
+ jint /*event_type*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return android::NO_INIT;
+}
+
+/**
+ * Request flush for al batch buffers.
+ */
+static int flush(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return android::NO_INIT;
+}
+
+
+static const JNINativeMethod sMethods[] = {
+ // {"name", "signature", (void*) functionPointer },
+ { "nativeClassInit", "()V", (void*) class_init },
+ { "nativeInitialize", "()V", (void*) initialize },
+ { "nativeRelease", "()V", (void*) release },
+ { "nativeIsSupported", "()Z", (void*) is_supported },
+ { "nativeGetSupportedActivities", "()[Ljava/lang/String;", (void*) get_supported_activities },
+ { "nativeEnableActivityEvent", "(IIJ)I", (void*) enable_activity_event },
+ { "nativeDisableActivityEvent", "(II)I", (void*) disable_activity_event },
+ { "nativeFlush", "()I", (void*) flush },
+};
+
+/**
+ * Registration method invoked in JNI load.
+ */
+int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env) {
+ return jniRegisterNativeMethods(
+ env,
+ "android/hardware/location/ActivityRecognitionHardware",
+ sMethods,
+ NELEM(sMethods));
+}
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 9b138eb..7eddcfe 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -16,8 +16,11 @@
#define LOG_TAG "NetUtils"
+#include <vector>
+
#include "jni.h"
#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
#include "NetdClient.h"
#include <utils/misc.h>
#include <android_runtime/AndroidRuntime.h>
@@ -55,6 +58,31 @@
static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest);
static const uint16_t kDhcpClientPort = 68;
+constexpr int MAXPACKETSIZE = 8 * 1024;
+// FrameworkListener limits the size of commands to 1024 bytes. TODO: fix this.
+constexpr int MAXCMDSIZE = 1024;
+
+static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
+ ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
+ if (detailMessage.get() == NULL) {
+ // Not really much we can do here. We're probably dead in the water,
+ // but let's try to stumble on...
+ env->ExceptionClear();
+ }
+ static jclass errnoExceptionClass =
+ MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException"));
+
+ static jmethodID errnoExceptionCtor =
+ GetMethodIDOrDie(env, errnoExceptionClass,
+ "<init>", "(Ljava/lang/String;I)V");
+
+ jobject exception = env->NewObject(errnoExceptionClass,
+ errnoExceptionCtor,
+ detailMessage.get(),
+ error);
+ env->Throw(reinterpret_cast<jthrowable>(exception));
+}
+
static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
{
struct sock_filter filter_code[] = {
@@ -372,6 +400,63 @@
}
}
+static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId,
+ jstring dname, jint ns_class, jint ns_type, jint flags) {
+ const jsize javaCharsCount = env->GetStringLength(dname);
+ const jsize byteCountUTF8 = env->GetStringUTFLength(dname);
+
+ // Only allow dname which could be simply formatted to UTF8.
+ // In native layer, res_mkquery would re-format the input char array to packet.
+ std::vector<char> queryname(byteCountUTF8 + 1, 0);
+
+ env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data());
+ int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags);
+
+ if (fd < 0) {
+ throwErrnoException(env, "resNetworkQuery", -fd);
+ return nullptr;
+ }
+
+ return jniCreateFileDescriptor(env, fd);
+}
+
+static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId,
+ jbyteArray msg, jint msgLen, jint flags) {
+ uint8_t data[MAXCMDSIZE];
+
+ checkLenAndCopy(env, msg, msgLen, data);
+ int fd = resNetworkSend(netId, data, msgLen, flags);
+
+ if (fd < 0) {
+ throwErrnoException(env, "resNetworkSend", -fd);
+ return nullptr;
+ }
+
+ return jniCreateFileDescriptor(env, fd);
+}
+
+static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ int rcode;
+ std::vector<uint8_t> buf(MAXPACKETSIZE, 0);
+
+ int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE);
+ if (res < 0) {
+ throwErrnoException(env, "resNetworkResult", -res);
+ return nullptr;
+ }
+
+ jbyteArray answer = env->NewByteArray(res);
+ if (answer == nullptr) {
+ throwErrnoException(env, "resNetworkResult", ENOMEM);
+ return nullptr;
+ } else {
+ env->SetByteArrayRegion(answer, 0, res,
+ reinterpret_cast<jbyte*>(buf.data()));
+ }
+
+ return answer;
+}
// ----------------------------------------------------------------------------
@@ -391,6 +476,9 @@
{ "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
{ "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter },
{ "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
+ { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
+ { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
+ { "resNetworkResult", "(Ljava/io/FileDescriptor;)[B", (void*) android_net_utils_resNetworkResult },
};
int register_android_net_NetworkUtils(JNIEnv* env)
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 7d63ec9..7837248 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -45,6 +45,7 @@
#include <meminfo/sysmeminfo.h>
#include <memtrack/memtrack.h>
#include <memunreachable/memunreachable.h>
+#include <android-base/strings.h>
#include "android_os_Debug.h"
namespace android
@@ -231,244 +232,162 @@
return err;
}
-static void read_mapinfo(FILE *fp, stats_t* stats, bool* foundSwapPss)
-{
- char line[1024];
- int len, nameLen;
- bool skip, done = false;
-
- unsigned pss = 0, swappable_pss = 0, rss = 0;
- float sharing_proportion = 0.0;
- unsigned shared_clean = 0, shared_dirty = 0;
- unsigned private_clean = 0, private_dirty = 0;
- unsigned swapped_out = 0, swapped_out_pss = 0;
- bool is_swappable = false;
- unsigned temp;
-
- uint64_t start;
- uint64_t end = 0;
- uint64_t prevEnd = 0;
- char* name;
- int name_pos;
-
- int whichHeap = HEAP_UNKNOWN;
- int subHeap = HEAP_UNKNOWN;
- int prevHeap = HEAP_UNKNOWN;
-
- *foundSwapPss = false;
-
- if(fgets(line, sizeof(line), fp) == 0) return;
-
- while (!done) {
- prevHeap = whichHeap;
- prevEnd = end;
- whichHeap = HEAP_UNKNOWN;
- subHeap = HEAP_UNKNOWN;
- skip = false;
- is_swappable = false;
-
- len = strlen(line);
- if (len < 1) return;
- line[--len] = 0;
-
- if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %*s %*x %*x:%*x %*d%n", &start, &end, &name_pos) != 2) {
- skip = true;
- } else {
- while (isspace(line[name_pos])) {
- name_pos += 1;
- }
- name = line + name_pos;
- nameLen = strlen(name);
- // Trim the end of the line if it is " (deleted)".
- const char* deleted_str = " (deleted)";
- if (nameLen > (int)strlen(deleted_str) &&
- strcmp(name+nameLen-strlen(deleted_str), deleted_str) == 0) {
- nameLen -= strlen(deleted_str);
- name[nameLen] = '\0';
- }
- if ((strstr(name, "[heap]") == name)) {
- whichHeap = HEAP_NATIVE;
- } else if (strncmp(name, "[anon:libc_malloc]", 18) == 0) {
- whichHeap = HEAP_NATIVE;
- } else if (strncmp(name, "[stack", 6) == 0) {
- whichHeap = HEAP_STACK;
- } else if (nameLen > 3 && strcmp(name+nameLen-3, ".so") == 0) {
- whichHeap = HEAP_SO;
- is_swappable = true;
- } else if (nameLen > 4 && strcmp(name+nameLen-4, ".jar") == 0) {
- whichHeap = HEAP_JAR;
- is_swappable = true;
- } else if (nameLen > 4 && strcmp(name+nameLen-4, ".apk") == 0) {
- whichHeap = HEAP_APK;
- is_swappable = true;
- } else if (nameLen > 4 && strcmp(name+nameLen-4, ".ttf") == 0) {
- whichHeap = HEAP_TTF;
- is_swappable = true;
- } else if ((nameLen > 4 && strstr(name, ".dex") != NULL) ||
- (nameLen > 5 && strcmp(name+nameLen-5, ".odex") == 0)) {
- whichHeap = HEAP_DEX;
- subHeap = HEAP_DEX_APP_DEX;
- is_swappable = true;
- } else if (nameLen > 5 && strcmp(name+nameLen-5, ".vdex") == 0) {
- whichHeap = HEAP_DEX;
- // Handle system@framework@boot* and system/framework/boot*
- if (strstr(name, "@boot") != NULL || strstr(name, "/boot") != NULL) {
- subHeap = HEAP_DEX_BOOT_VDEX;
- } else {
- subHeap = HEAP_DEX_APP_VDEX;
- }
- is_swappable = true;
- } else if (nameLen > 4 && strcmp(name+nameLen-4, ".oat") == 0) {
- whichHeap = HEAP_OAT;
- is_swappable = true;
- } else if (nameLen > 4 && strcmp(name+nameLen-4, ".art") == 0) {
- whichHeap = HEAP_ART;
- // Handle system@framework@boot* and system/framework/boot*
- if (strstr(name, "@boot") != NULL || strstr(name, "/boot") != NULL) {
- subHeap = HEAP_ART_BOOT;
- } else {
- subHeap = HEAP_ART_APP;
- }
- is_swappable = true;
- } else if (strncmp(name, "/dev/", 5) == 0) {
- whichHeap = HEAP_UNKNOWN_DEV;
- if (strncmp(name, "/dev/kgsl-3d0", 13) == 0) {
- whichHeap = HEAP_GL_DEV;
- } else if (strncmp(name, "/dev/ashmem/CursorWindow", 24) == 0) {
- whichHeap = HEAP_CURSOR;
- } else if (strncmp(name, "/dev/ashmem", 11)) {
- whichHeap = HEAP_ASHMEM;
- }
- } else if (strncmp(name, "[anon:", 6) == 0) {
- whichHeap = HEAP_UNKNOWN;
- if (strncmp(name, "[anon:dalvik-", 13) == 0) {
- whichHeap = HEAP_DALVIK_OTHER;
- if (strstr(name, "[anon:dalvik-LinearAlloc") == name) {
- subHeap = HEAP_DALVIK_OTHER_LINEARALLOC;
- } else if ((strstr(name, "[anon:dalvik-alloc space") == name) ||
- (strstr(name, "[anon:dalvik-main space") == name)) {
- // This is the regular Dalvik heap.
- whichHeap = HEAP_DALVIK;
- subHeap = HEAP_DALVIK_NORMAL;
- } else if (strstr(name, "[anon:dalvik-large object space") == name ||
- strstr(name, "[anon:dalvik-free list large object space")
- == name) {
- whichHeap = HEAP_DALVIK;
- subHeap = HEAP_DALVIK_LARGE;
- } else if (strstr(name, "[anon:dalvik-non moving space") == name) {
- whichHeap = HEAP_DALVIK;
- subHeap = HEAP_DALVIK_NON_MOVING;
- } else if (strstr(name, "[anon:dalvik-zygote space") == name) {
- whichHeap = HEAP_DALVIK;
- subHeap = HEAP_DALVIK_ZYGOTE;
- } else if (strstr(name, "[anon:dalvik-indirect ref") == name) {
- subHeap = HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE;
- } else if (strstr(name, "[anon:dalvik-jit-code-cache") == name ||
- strstr(name, "[anon:dalvik-data-code-cache") == name) {
- subHeap = HEAP_DALVIK_OTHER_CODE_CACHE;
- } else if (strstr(name, "[anon:dalvik-CompilerMetadata") == name) {
- subHeap = HEAP_DALVIK_OTHER_COMPILER_METADATA;
- } else {
- subHeap = HEAP_DALVIK_OTHER_ACCOUNTING; // Default to accounting.
- }
- }
- } else if (nameLen > 0) {
- whichHeap = HEAP_UNKNOWN_MAP;
- } else if (start == prevEnd && prevHeap == HEAP_SO) {
- // bss section of a shared library.
- whichHeap = HEAP_SO;
- }
- }
-
- //ALOGI("native=%d dalvik=%d sqlite=%d: %s\n", isNativeHeap, isDalvikHeap,
- // isSqliteHeap, line);
-
- shared_clean = 0;
- shared_dirty = 0;
- private_clean = 0;
- private_dirty = 0;
- swapped_out = 0;
- swapped_out_pss = 0;
-
- while (true) {
- if (fgets(line, 1024, fp) == 0) {
- done = true;
- break;
- }
-
- if (line[0] == 'S' && sscanf(line, "Size: %d kB", &temp) == 1) {
- /* size = temp; */
- } else if (line[0] == 'R' && sscanf(line, "Rss: %d kB", &temp) == 1) {
- rss = temp;
- } else if (line[0] == 'P' && sscanf(line, "Pss: %d kB", &temp) == 1) {
- pss = temp;
- } else if (line[0] == 'S' && sscanf(line, "Shared_Clean: %d kB", &temp) == 1) {
- shared_clean = temp;
- } else if (line[0] == 'S' && sscanf(line, "Shared_Dirty: %d kB", &temp) == 1) {
- shared_dirty = temp;
- } else if (line[0] == 'P' && sscanf(line, "Private_Clean: %d kB", &temp) == 1) {
- private_clean = temp;
- } else if (line[0] == 'P' && sscanf(line, "Private_Dirty: %d kB", &temp) == 1) {
- private_dirty = temp;
- } else if (line[0] == 'R' && sscanf(line, "Referenced: %d kB", &temp) == 1) {
- /* referenced = temp; */
- } else if (line[0] == 'S' && sscanf(line, "Swap: %d kB", &temp) == 1) {
- swapped_out = temp;
- } else if (line[0] == 'S' && sscanf(line, "SwapPss: %d kB", &temp) == 1) {
- *foundSwapPss = true;
- swapped_out_pss = temp;
- } else if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %*s %*x %*x:%*x %*d", &start, &end) == 2) {
- // looks like a new mapping
- // example: "10000000-10001000 ---p 10000000 00:00 0"
- break;
- }
- }
-
- if (!skip) {
- if (is_swappable && (pss > 0)) {
- sharing_proportion = 0.0;
- if ((shared_clean > 0) || (shared_dirty > 0)) {
- sharing_proportion = (pss - private_clean
- - private_dirty)/(shared_clean+shared_dirty);
- }
- swappable_pss = (sharing_proportion*shared_clean) + private_clean;
- } else
- swappable_pss = 0;
-
- stats[whichHeap].pss += pss;
- stats[whichHeap].swappablePss += swappable_pss;
- stats[whichHeap].rss += rss;
- stats[whichHeap].privateDirty += private_dirty;
- stats[whichHeap].sharedDirty += shared_dirty;
- stats[whichHeap].privateClean += private_clean;
- stats[whichHeap].sharedClean += shared_clean;
- stats[whichHeap].swappedOut += swapped_out;
- stats[whichHeap].swappedOutPss += swapped_out_pss;
- if (whichHeap == HEAP_DALVIK || whichHeap == HEAP_DALVIK_OTHER ||
- whichHeap == HEAP_DEX || whichHeap == HEAP_ART) {
- stats[subHeap].pss += pss;
- stats[subHeap].swappablePss += swappable_pss;
- stats[subHeap].rss += rss;
- stats[subHeap].privateDirty += private_dirty;
- stats[subHeap].sharedDirty += shared_dirty;
- stats[subHeap].privateClean += private_clean;
- stats[subHeap].sharedClean += shared_clean;
- stats[subHeap].swappedOut += swapped_out;
- stats[subHeap].swappedOutPss += swapped_out_pss;
- }
- }
- }
-}
-
static void load_maps(int pid, stats_t* stats, bool* foundSwapPss)
{
*foundSwapPss = false;
+ uint64_t prev_end = 0;
+ int prev_heap = HEAP_UNKNOWN;
std::string smaps_path = base::StringPrintf("/proc/%d/smaps", pid);
- UniqueFile fp = MakeUniqueFile(smaps_path.c_str(), "re");
- if (fp == nullptr) return;
+ auto vma_scan = [&](const meminfo::Vma& vma) {
+ int which_heap = HEAP_UNKNOWN;
+ int sub_heap = HEAP_UNKNOWN;
+ bool is_swappable = false;
+ std::string name;
+ if (base::EndsWith(vma.name, " (deleted)")) {
+ name = vma.name.substr(0, vma.name.size() - strlen(" (deleted)"));
+ } else {
+ name = vma.name;
+ }
- read_mapinfo(fp.get(), stats, foundSwapPss);
+ uint32_t namesz = name.size();
+ if (base::StartsWith(name, "[heap]")) {
+ which_heap = HEAP_NATIVE;
+ } else if (base::StartsWith(name, "[anon:libc_malloc]")) {
+ which_heap = HEAP_NATIVE;
+ } else if (base::StartsWith(name, "[stack")) {
+ which_heap = HEAP_NATIVE;
+ } else if (base::EndsWith(name, ".so")) {
+ which_heap = HEAP_SO;
+ is_swappable = true;
+ } else if (base::EndsWith(name, ".jar")) {
+ which_heap = HEAP_JAR;
+ is_swappable = true;
+ } else if (base::EndsWith(name, ".apk")) {
+ which_heap = HEAP_APK;
+ is_swappable = true;
+ } else if (base::EndsWith(name, ".ttf")) {
+ which_heap = HEAP_TTF;
+ is_swappable = true;
+ } else if ((base::EndsWith(name, ".odex")) ||
+ (namesz > 4 && strstr(name.c_str(), ".dex") != nullptr)) {
+ which_heap = HEAP_DEX;
+ sub_heap = HEAP_DEX_APP_DEX;
+ is_swappable = true;
+ } else if (base::EndsWith(name, ".vdex")) {
+ which_heap = HEAP_DEX;
+ // Handle system@framework@boot and system/framework/boot
+ if ((strstr(name.c_str(), "@boot") != nullptr) ||
+ (strstr(name.c_str(), "/boot"))) {
+ sub_heap = HEAP_DEX_BOOT_VDEX;
+ } else {
+ sub_heap = HEAP_DEX_APP_VDEX;
+ }
+ is_swappable = true;
+ } else if (base::EndsWith(name, ".oat")) {
+ which_heap = HEAP_OAT;
+ is_swappable = true;
+ } else if (base::EndsWith(name, ".art")) {
+ which_heap = HEAP_ART;
+ // Handle system@framework@boot* and system/framework/boot*
+ if ((strstr(name.c_str(), "@boot") != nullptr) ||
+ (strstr(name.c_str(), "/boot"))) {
+ sub_heap = HEAP_DEX_BOOT_VDEX;
+ } else {
+ sub_heap = HEAP_DEX_APP_VDEX;
+ }
+ is_swappable = true;
+ } else if (base::StartsWith(name, "/dev/")) {
+ which_heap = HEAP_UNKNOWN_DEV;
+ if (base::StartsWith(name, "/dev/kgsl-3d0")) {
+ which_heap = HEAP_GL_DEV;
+ } else if (base::StartsWith(name, "/dev/ashmem/CursorWindow")) {
+ which_heap = HEAP_CURSOR;
+ } else if (base::StartsWith(name, "/dev/ashmem")) {
+ which_heap = HEAP_ASHMEM;
+ }
+ } else if (base::StartsWith(name, "[anon:")) {
+ which_heap = HEAP_UNKNOWN;
+ if (base::StartsWith(name, "[anon:dalvik-")) {
+ which_heap = HEAP_DALVIK_OTHER;
+ if (base::StartsWith(name, "[anon:dalvik-LinearAlloc")) {
+ sub_heap = HEAP_DALVIK_OTHER_LINEARALLOC;
+ } else if (base::StartsWith(name, "[anon:dalvik-alloc space") ||
+ base::StartsWith(name, "[anon:dalvik-main space")) {
+ // This is the regular Dalvik heap.
+ which_heap = HEAP_DALVIK;
+ sub_heap = HEAP_DALVIK_NORMAL;
+ } else if (base::StartsWith(name,
+ "[anon:dalvik-large object space") ||
+ base::StartsWith(
+ name, "[anon:dalvik-free list large object space")) {
+ which_heap = HEAP_DALVIK;
+ sub_heap = HEAP_DALVIK_LARGE;
+ } else if (base::StartsWith(name, "[anon:dalvik-non moving space")) {
+ which_heap = HEAP_DALVIK;
+ sub_heap = HEAP_DALVIK_NON_MOVING;
+ } else if (base::StartsWith(name, "[anon:dalvik-zygote space")) {
+ which_heap = HEAP_DALVIK;
+ sub_heap = HEAP_DALVIK_ZYGOTE;
+ } else if (base::StartsWith(name, "[anon:dalvik-indirect ref")) {
+ sub_heap = HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE;
+ } else if (base::StartsWith(name, "[anon:dalvik-jit-code-cache") ||
+ base::StartsWith(name, "[anon:dalvik-data-code-cache")) {
+ sub_heap = HEAP_DALVIK_OTHER_CODE_CACHE;
+ } else if (base::StartsWith(name, "[anon:dalvik-CompilerMetadata")) {
+ sub_heap = HEAP_DALVIK_OTHER_COMPILER_METADATA;
+ } else {
+ sub_heap = HEAP_DALVIK_OTHER_ACCOUNTING; // Default to accounting.
+ }
+ }
+ } else if (namesz > 0) {
+ which_heap = HEAP_UNKNOWN_MAP;
+ } else if (vma.start == prev_end && prev_heap == HEAP_SO) {
+ // bss section of a shared library
+ which_heap = HEAP_SO;
+ }
+
+ prev_end = vma.end;
+ prev_heap = which_heap;
+
+ const meminfo::MemUsage& usage = vma.usage;
+ if (usage.swap_pss > 0 && *foundSwapPss != true) {
+ *foundSwapPss = true;
+ }
+
+ uint64_t swapable_pss = 0;
+ if (is_swappable && (usage.pss > 0)) {
+ float sharing_proportion = 0.0;
+ if ((usage.shared_clean > 0) || (usage.shared_dirty > 0)) {
+ sharing_proportion = (usage.pss - usage.uss) / (usage.shared_clean + usage.shared_dirty);
+ }
+ swapable_pss = (sharing_proportion * usage.shared_clean) + usage.private_clean;
+ }
+
+ stats[which_heap].pss += usage.pss;
+ stats[which_heap].swappablePss += swapable_pss;
+ stats[which_heap].rss += usage.rss;
+ stats[which_heap].privateDirty += usage.private_dirty;
+ stats[which_heap].sharedDirty += usage.shared_dirty;
+ stats[which_heap].privateClean += usage.private_clean;
+ stats[which_heap].sharedClean += usage.shared_clean;
+ stats[which_heap].swappedOut += usage.swap;
+ stats[which_heap].swappedOutPss += usage.swap_pss;
+ if (which_heap == HEAP_DALVIK || which_heap == HEAP_DALVIK_OTHER ||
+ which_heap == HEAP_DEX || which_heap == HEAP_ART) {
+ stats[sub_heap].pss += usage.pss;
+ stats[sub_heap].swappablePss += swapable_pss;
+ stats[sub_heap].rss += usage.rss;
+ stats[sub_heap].privateDirty += usage.private_dirty;
+ stats[sub_heap].sharedDirty += usage.shared_dirty;
+ stats[sub_heap].privateClean += usage.private_clean;
+ stats[sub_heap].sharedClean += usage.shared_clean;
+ stats[sub_heap].swappedOut += usage.swap;
+ stats[sub_heap].swappedOutPss += usage.swap_pss;
+ }
+ };
+
+ meminfo::ForEachVmaFromFile(smaps_path, vma_scan);
}
static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz,
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 897427f..69877c7 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -126,7 +126,12 @@
jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject,
jint windowType, jint ownerUid) {
ScopedUtfChars name(env, nameStr);
- sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj));
+ sp<SurfaceComposerClient> client;
+ if (sessionObj != NULL) {
+ client = android_view_SurfaceSession_getClient(env, sessionObj);
+ } else {
+ client = SurfaceComposerClient::getDefault();
+ }
SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject);
sp<SurfaceControl> surface;
status_t err = client->createSurfaceChecked(
@@ -277,6 +282,21 @@
transaction->setPosition(ctrl, x, y);
}
+static void nativeSetGeometry(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+ jobject sourceObj, jobject dstObj, jlong orientation) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+
+ Rect source, dst;
+ if (sourceObj != NULL) {
+ source = rectFromObj(env, sourceObj);
+ }
+ if (dstObj != NULL) {
+ dst = rectFromObj(env, dstObj);
+ }
+ transaction->setGeometry(ctrl, source, dst, orientation);
+}
+
static void nativeSetGeometryAppliesWithResize(JNIEnv* env, jclass clazz,
jlong transactionObj,
jlong nativeObject) {
@@ -340,7 +360,7 @@
jlong nativeObject, jobject inputWindow) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- sp<NativeInputWindowHandle> handle = android_server_InputWindowHandle_getHandle(
+ sp<NativeInputWindowHandle> handle = android_view_InputWindowHandle_getHandle(
env, inputWindow);
handle->updateInfo();
@@ -868,13 +888,13 @@
static void nativeReparent(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject,
- jobject newParentObject) {
+ jlong newParentObject) {
auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
- sp<IBinder> parentHandle = ibinderForJavaObject(env, newParentObject);
+ auto newParent = reinterpret_cast<SurfaceControl *>(newParentObject);
{
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- transaction->reparent(ctrl, parentHandle);
+ transaction->reparent(ctrl, newParent != NULL ? newParent->getHandle() : NULL);
}
}
@@ -1063,7 +1083,7 @@
(void*)nativeDeferTransactionUntilSurface },
{"nativeReparentChildren", "(JJLandroid/os/IBinder;)V",
(void*)nativeReparentChildren } ,
- {"nativeReparent", "(JJLandroid/os/IBinder;)V",
+ {"nativeReparent", "(JJJ)V",
(void*)nativeReparent },
{"nativeSeverChildren", "(JJ)V",
(void*)nativeSeverChildren } ,
@@ -1087,6 +1107,8 @@
{"nativeGetDisplayedContentSample",
"(Landroid/os/IBinder;JJ)Landroid/hardware/display/DisplayedContentSample;",
(void*)nativeGetDisplayedContentSample },
+ {"nativeSetGeometry", "(JJLandroid/graphics/Rect;Landroid/graphics/Rect;J)V",
+ (void*)nativeSetGeometry }
};
int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 5a8ab3c..318ec9b 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -48,6 +48,7 @@
#include <FrameInfo.h>
#include <FrameMetricsObserver.h>
#include <IContextFactory.h>
+#include <Picture.h>
#include <Properties.h>
#include <PropertyValuesAnimatorSet.h>
#include <RenderNode.h>
@@ -71,6 +72,11 @@
} gFrameMetricsObserverClassInfo;
struct {
+ jclass clazz;
+ jmethodID invokePictureCapturedCallback;
+} gHardwareRenderer;
+
+struct {
jmethodID onFrameDraw;
} gFrameDrawingCallback;
@@ -905,6 +911,27 @@
jobject mObject;
};
+static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv* env,
+ jobject clazz, jlong proxyPtr, jobject pictureCallback) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ if (!pictureCallback) {
+ proxy->setPictureCapturedCallback(nullptr);
+ } else {
+ JavaVM* vm = nullptr;
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
+ auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm,
+ env->NewGlobalRef(pictureCallback));
+ proxy->setPictureCapturedCallback([globalCallbackRef](sk_sp<SkPicture>&& picture) {
+ JNIEnv* env = getenv(globalCallbackRef->vm());
+ Picture* wrapper = new Picture{std::move(picture)};
+ env->CallStaticVoidMethod(gHardwareRenderer.clazz,
+ gHardwareRenderer.invokePictureCapturedCallback,
+ static_cast<jlong>(reinterpret_cast<intptr_t>(wrapper)),
+ globalCallbackRef->object());
+ });
+ }
+}
+
static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env,
jobject clazz, jlong proxyPtr, jobject frameCallback) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -1145,6 +1172,8 @@
{ "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
{ "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
{ "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds},
+ { "nSetPictureCaptureCallback", "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V",
+ (void*) android_view_ThreadedRenderer_setPictureCapturedCallbackJNI },
{ "nSetFrameCallback", "(JLandroid/graphics/HardwareRenderer$FrameDrawingCallback;)V",
(void*)android_view_ThreadedRenderer_setFrameCallback},
{ "nSetFrameCompleteCallback", "(JLandroid/graphics/HardwareRenderer$FrameCompleteCallback;)V",
@@ -1198,6 +1227,13 @@
gFrameMetricsObserverClassInfo.timingDataBuffer = GetFieldIDOrDie(
env, metricsClass, "mTimingData", "[J");
+ jclass hardwareRenderer = FindClassOrDie(env,
+ "android/graphics/HardwareRenderer");
+ gHardwareRenderer.clazz = reinterpret_cast<jclass>(env->NewGlobalRef(hardwareRenderer));
+ gHardwareRenderer.invokePictureCapturedCallback = GetStaticMethodIDOrDie(env, hardwareRenderer,
+ "invokePictureCapturedCallback",
+ "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V");
+
jclass frameCallbackClass = FindClassOrDie(env,
"android/graphics/HardwareRenderer$FrameDrawingCallback");
gFrameDrawingCallback.onFrameDraw = GetMethodIDOrDie(env, frameCallbackClass,
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index e81b627..8681d4b 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -29,13 +29,17 @@
#include <sys/mount.h>
#include <linux/fs.h>
+#include <array>
+#include <atomic>
#include <functional>
#include <list>
#include <optional>
#include <sstream>
#include <string>
+#include <string_view>
#include <android/fdsan.h>
+#include <arpa/inet.h>
#include <fcntl.h>
#include <grp.h>
#include <inttypes.h>
@@ -46,9 +50,11 @@
#include <stdlib.h>
#include <sys/capability.h>
#include <sys/cdefs.h>
+#include <sys/eventfd.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/resource.h>
+#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
@@ -56,10 +62,11 @@
#include <sys/wait.h>
#include <unistd.h>
-#include "android-base/logging.h"
+#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/file.h>
#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
#include <cutils/fs.h>
#include <cutils/multiuser.h>
#include <private/android_filesystem_config.h>
@@ -81,6 +88,9 @@
namespace {
+// TODO (chriswailes): Add a function to initialize native Zygote data.
+// TODO (chriswailes): Fix mixed indentation style (2 and 4 spaces).
+
using namespace std::placeholders;
using android::String8;
@@ -92,6 +102,9 @@
#define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \
append(StringPrintf(__VA_ARGS__))
+// This type is duplicated in fd_utils.h
+typedef const std::function<void(std::string)>& fail_fn_t;
+
static pid_t gSystemServerPid = 0;
static const char kIsolatedStorage[] = "persist.sys.isolated_storage";
@@ -103,6 +116,152 @@
static bool g_is_security_enforced = true;
+/**
+ * The maximum number of characters (not including a null terminator) that a
+ * process name may contain.
+ */
+static constexpr size_t MAX_NAME_LENGTH = 15;
+
+/**
+ * The prefix string for environmental variables storing socket FDs created by
+ * init.
+ */
+
+static constexpr std::string_view ANDROID_SOCKET_PREFIX("ANDROID_SOCKET_");
+
+/**
+ * The file descriptor for the Zygote socket opened by init.
+ */
+
+static int gZygoteSocketFD = -1;
+
+/**
+ * The file descriptor for the Blastula pool socket opened by init.
+ */
+
+static int gBlastulaPoolSocketFD = -1;
+
+/**
+ * The number of Blastulas currently in this Zygote's pool.
+ */
+static std::atomic_uint32_t gBlastulaPoolCount = 0;
+
+/**
+ * Event file descriptor used to communicate reaped blastulas to the
+ * ZygoteServer.
+ */
+static int gBlastulaPoolEventFD = -1;
+
+/**
+ * The maximum value that the gBlastulaPoolMax variable may take. This value
+ * is a mirror of Zygote.BLASTULA_POOL_MAX_LIMIT
+ */
+static constexpr int BLASTULA_POOL_MAX_LIMIT = 10;
+
+/**
+ * A helper class containing accounting information for Blastulas.
+ */
+class BlastulaTableEntry {
+ public:
+ struct EntryStorage {
+ int32_t pid;
+ int32_t read_pipe_fd;
+
+ bool operator!=(const EntryStorage& other) {
+ return pid != other.pid || read_pipe_fd != other.read_pipe_fd;
+ }
+ };
+
+ private:
+ static constexpr EntryStorage INVALID_ENTRY_VALUE = {-1, -1};
+
+ std::atomic<EntryStorage> mStorage;
+ static_assert(decltype(mStorage)::is_always_lock_free);
+
+ public:
+ constexpr BlastulaTableEntry() : mStorage(INVALID_ENTRY_VALUE) {}
+
+ /**
+ * If the provided PID matches the one stored in this entry, the entry will
+ * be invalidated and the associated file descriptor will be closed. If the
+ * PIDs don't match nothing will happen.
+ *
+ * @param pid The ID of the process who's entry we want to clear.
+ * @return True if the entry was cleared; false otherwise
+ */
+ bool ClearForPID(int32_t pid) {
+ EntryStorage storage = mStorage.load();
+
+ if (storage.pid == pid) {
+ /*
+ * There are three possible outcomes from this compare-and-exchange:
+ * 1) It succeeds, in which case we close the FD
+ * 2) It fails and the new value is INVALID_ENTRY_VALUE, in which case
+ * the entry has already been cleared.
+ * 3) It fails and the new value isn't INVALID_ENTRY_VALUE, in which
+ * case the entry has already been cleared and re-used.
+ *
+ * In all three cases the goal of the caller has been met and we can
+ * return true.
+ */
+ if (mStorage.compare_exchange_strong(storage, INVALID_ENTRY_VALUE)) {
+ close(storage.read_pipe_fd);
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @return A copy of the data stored in this entry.
+ */
+ std::optional<EntryStorage> GetValues() {
+ EntryStorage storage = mStorage.load();
+
+ if (storage != INVALID_ENTRY_VALUE) {
+ return storage;
+ } else {
+ return std::nullopt;
+ }
+ }
+
+ /**
+ * Sets the entry to the given values if it is currently invalid.
+ *
+ * @param pid The process ID for the new entry.
+ * @param read_pipe_fd The read end of the blastula control pipe for this
+ * process.
+ * @return True if the entry was set; false otherwise.
+ */
+ bool SetIfInvalid(int32_t pid, int32_t read_pipe_fd) {
+ EntryStorage new_value_storage;
+
+ new_value_storage.pid = pid;
+ new_value_storage.read_pipe_fd = read_pipe_fd;
+
+ EntryStorage expected = INVALID_ENTRY_VALUE;
+
+ return mStorage.compare_exchange_strong(expected, new_value_storage);
+ }
+};
+
+/**
+ * A table containing information about the Blastulas currently in the pool.
+ *
+ * Multiple threads may be attempting to modify the table, either from the
+ * signal handler or from the ZygoteServer poll loop. Atomic loads/stores in
+ * the BlastulaTableEntry class prevent data races during these concurrent
+ * operations.
+ */
+static std::array<BlastulaTableEntry, BLASTULA_POOL_MAX_LIMIT> gBlastulaTable;
+
+/**
+ * The list of open zygote file descriptors.
+ */
+static FileDescriptorTable* gOpenFdTable = nullptr;
+
// Must match values in com.android.internal.os.Zygote.
enum MountExternalKind {
MOUNT_EXTERNAL_NONE = 0,
@@ -119,6 +278,9 @@
DEBUG_ENABLE_JDWP = 1,
};
+// Forward declaration so we don't have to move the signal handler.
+static bool RemoveBlastulaTableEntry(pid_t blastula_pid);
+
static void RuntimeAbort(JNIEnv* env, int line, const char* msg) {
std::ostringstream oss;
oss << __FILE__ << ":" << line << ": " << msg;
@@ -129,6 +291,7 @@
static void SigChldHandler(int /*signal_number*/) {
pid_t pid;
int status;
+ int64_t blastulas_removed = 0;
// It's necessary to save and restore the errno during this function.
// Since errno is stored per thread, changing it here modifies the errno
@@ -162,6 +325,11 @@
ALOGE("Exit zygote because system server (%d) has terminated", pid);
kill(getpid(), SIGKILL);
}
+
+ // Check to see if the PID is in the blastula pool and remove it if it is.
+ if (RemoveBlastulaTableEntry(pid)) {
+ ++blastulas_removed;
+ }
}
// Note that we shouldn't consider ECHILD an error because
@@ -170,6 +338,15 @@
ALOGW("Zygote SIGCHLD error in waitpid: %s", strerror(errno));
}
+ if (blastulas_removed > 0) {
+ if (write(gBlastulaPoolEventFD, &blastulas_removed, sizeof(blastulas_removed)) == -1) {
+ // If this write fails something went terribly wrong. We will now kill
+ // the zygote and let the system bring it back up.
+ ALOGE("Zygote failed to write to blastula pool event FD: %s", strerror(errno));
+ kill(getpid(), SIGKILL);
+ }
+ }
+
errno = saved_errno;
}
@@ -194,13 +371,13 @@
struct sigaction sig_chld = {};
sig_chld.sa_handler = SigChldHandler;
- if (sigaction(SIGCHLD, &sig_chld, NULL) < 0) {
+ if (sigaction(SIGCHLD, &sig_chld, nullptr) < 0) {
ALOGW("Error setting SIGCHLD handler: %s", strerror(errno));
}
struct sigaction sig_hup = {};
sig_hup.sa_handler = SIG_IGN;
- if (sigaction(SIGHUP, &sig_hup, NULL) < 0) {
+ if (sigaction(SIGHUP, &sig_hup, nullptr) < 0) {
ALOGW("Error setting SIGHUP handler: %s", strerror(errno));
}
}
@@ -211,64 +388,57 @@
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_DFL;
- if (sigaction(SIGCHLD, &sa, NULL) < 0) {
+ if (sigaction(SIGCHLD, &sa, nullptr) < 0) {
ALOGW("Error unsetting SIGCHLD handler: %s", strerror(errno));
}
}
// Calls POSIX setgroups() using the int[] object as an argument.
-// A NULL argument is tolerated.
-static bool SetGids(JNIEnv* env, jintArray javaGids, std::string* error_msg) {
- if (javaGids == NULL) {
- return true;
+// A nullptr argument is tolerated.
+static void SetGids(JNIEnv* env, jintArray managed_gids, fail_fn_t fail_fn) {
+ if (managed_gids == nullptr) {
+ return;
}
- ScopedIntArrayRO gids(env, javaGids);
- if (gids.get() == NULL) {
- *error_msg = CREATE_ERROR("Getting gids int array failed");
- return false;
- }
- int rc = setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0]));
- if (rc == -1) {
- *error_msg = CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size());
- return false;
+ ScopedIntArrayRO gids(env, managed_gids);
+ if (gids.get() == nullptr) {
+ fail_fn(CREATE_ERROR("Getting gids int array failed"));
}
- return true;
+ if (setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0])) == -1) {
+ fail_fn(CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size()));
+ }
}
// Sets the resource limits via setrlimit(2) for the values in the
// two-dimensional array of integers that's passed in. The second dimension
-// contains a tuple of length 3: (resource, rlim_cur, rlim_max). NULL is
+// contains a tuple of length 3: (resource, rlim_cur, rlim_max). nullptr is
// treated as an empty array.
-static bool SetRLimits(JNIEnv* env, jobjectArray javaRlimits, std::string* error_msg) {
- if (javaRlimits == NULL) {
- return true;
+static void SetRLimits(JNIEnv* env, jobjectArray managed_rlimits, fail_fn_t fail_fn) {
+ if (managed_rlimits == nullptr) {
+ return;
}
rlimit rlim;
memset(&rlim, 0, sizeof(rlim));
- for (int i = 0; i < env->GetArrayLength(javaRlimits); ++i) {
- ScopedLocalRef<jobject> javaRlimitObject(env, env->GetObjectArrayElement(javaRlimits, i));
- ScopedIntArrayRO javaRlimit(env, reinterpret_cast<jintArray>(javaRlimitObject.get()));
- if (javaRlimit.size() != 3) {
- *error_msg = CREATE_ERROR("rlimits array must have a second dimension of size 3");
- return false;
+ for (int i = 0; i < env->GetArrayLength(managed_rlimits); ++i) {
+ ScopedLocalRef<jobject>
+ managed_rlimit_object(env, env->GetObjectArrayElement(managed_rlimits, i));
+ ScopedIntArrayRO rlimit_handle(env, reinterpret_cast<jintArray>(managed_rlimit_object.get()));
+
+ if (rlimit_handle.size() != 3) {
+ fail_fn(CREATE_ERROR("rlimits array must have a second dimension of size 3"));
}
- rlim.rlim_cur = javaRlimit[1];
- rlim.rlim_max = javaRlimit[2];
+ rlim.rlim_cur = rlimit_handle[1];
+ rlim.rlim_max = rlimit_handle[2];
- int rc = setrlimit(javaRlimit[0], &rlim);
- if (rc == -1) {
- *error_msg = CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed", javaRlimit[0], rlim.rlim_cur,
- rlim.rlim_max);
- return false;
+ if (setrlimit(rlimit_handle[0], &rlim) == -1) {
+ fail_fn(CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed",
+ rlimit_handle[0], rlim.rlim_cur, rlim.rlim_max));
}
}
-
- return true;
}
static void EnableDebugger() {
@@ -323,10 +493,7 @@
// Apply system or app filter based on uid.
if (uid >= AID_APP_START) {
if (is_child_zygote) {
- // set_app_zygote_seccomp_filter();
- // TODO(b/111434506) install the filter; for now, install the app filter
- // which is more restrictive.
- set_app_seccomp_filter();
+ set_app_zygote_seccomp_filter();
} else {
set_app_seccomp_filter();
}
@@ -335,32 +502,26 @@
}
}
-static bool EnableKeepCapabilities(std::string* error_msg) {
- int rc = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
- if (rc == -1) {
- *error_msg = CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno));
- return false;
+static void EnableKeepCapabilities(fail_fn_t fail_fn) {
+ if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
+ fail_fn(CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno)));
}
- return true;
}
-static bool DropCapabilitiesBoundingSet(std::string* error_msg) {
- for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {
- int rc = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
- if (rc == -1) {
+static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn) {
+ for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {;
+ if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0) == -1) {
if (errno == EINVAL) {
ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify "
"your kernel is compiled with file capabilities support");
} else {
- *error_msg = CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno)));
}
}
}
- return true;
}
-static bool SetInheritable(uint64_t inheritable, std::string* error_msg) {
+static void SetInheritable(uint64_t inheritable, fail_fn_t fail_fn) {
__user_cap_header_struct capheader;
memset(&capheader, 0, sizeof(capheader));
capheader.version = _LINUX_CAPABILITY_VERSION_3;
@@ -368,23 +529,19 @@
__user_cap_data_struct capdata[2];
if (capget(&capheader, &capdata[0]) == -1) {
- *error_msg = CREATE_ERROR("capget failed: %s", strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("capget failed: %s", strerror(errno)));
}
capdata[0].inheritable = inheritable;
capdata[1].inheritable = inheritable >> 32;
if (capset(&capheader, &capdata[0]) == -1) {
- *error_msg = CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno)));
}
-
- return true;
}
-static bool SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable,
- std::string* error_msg) {
+static void SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable,
+ fail_fn_t fail_fn) {
__user_cap_header_struct capheader;
memset(&capheader, 0, sizeof(capheader));
capheader.version = _LINUX_CAPABILITY_VERSION_3;
@@ -400,27 +557,23 @@
capdata[1].inheritable = inheritable >> 32;
if (capset(&capheader, &capdata[0]) == -1) {
- *error_msg = CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") "
- "failed: %s", permitted, effective, inheritable, strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") "
+ "failed: %s", permitted, effective, inheritable, strerror(errno)));
}
- return true;
}
-static bool SetSchedulerPolicy(std::string* error_msg) {
+static void SetSchedulerPolicy(fail_fn_t fail_fn) {
errno = -set_sched_policy(0, SP_DEFAULT);
if (errno != 0) {
- *error_msg = CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno)));
}
- return true;
}
static int UnmountTree(const char* path) {
size_t path_len = strlen(path);
FILE* fp = setmntent("/proc/mounts", "r");
- if (fp == NULL) {
+ if (fp == nullptr) {
ALOGE("Error opening /proc/mounts: %s", strerror(errno));
return -errno;
}
@@ -429,7 +582,7 @@
// reverse order to give us the best chance of success.
std::list<std::string> toUnmount;
mntent* mentry;
- while ((mentry = getmntent(fp)) != NULL) {
+ while ((mentry = getmntent(fp)) != nullptr) {
if (strncmp(mentry->mnt_dir, path, path_len) == 0) {
toUnmount.push_front(std::string(mentry->mnt_dir));
}
@@ -444,56 +597,55 @@
return 0;
}
-static bool createPkgSandbox(uid_t uid, const std::string& package_name, std::string* error_msg) {
+static void CreatePkgSandbox(uid_t uid, const std::string& package_name, fail_fn_t fail_fn) {
// Create /mnt/user/0/package/<package-name>
userid_t user_id = multiuser_get_user_id(uid);
std::string pkg_sandbox_dir = StringPrintf("/mnt/user/%d", user_id);
if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0751, AID_ROOT, AID_ROOT) != 0) {
- *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str());
- return false;
+ fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()));
}
+
StringAppendF(&pkg_sandbox_dir, "/package");
if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0700, AID_ROOT, AID_ROOT) != 0) {
- *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str());
- return false;
+ fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()));
}
+
StringAppendF(&pkg_sandbox_dir, "/%s", package_name.c_str());
if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0755, uid, uid) != 0) {
- *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str());
- return false;
+ fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()));
}
- return true;
}
-static bool bindMount(const std::string& sourceDir, const std::string& targetDir,
- std::string* error_msg) {
- if (TEMP_FAILURE_RETRY(mount(sourceDir.c_str(), targetDir.c_str(),
- nullptr, MS_BIND | MS_REC, nullptr)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to %s: %s",
- sourceDir.c_str(), targetDir.c_str(), strerror(errno));
- return false;
+static void BindMount(const std::string& sourceDir, const std::string& targetDir,
+ fail_fn_t fail_fn) {
+ if (TEMP_FAILURE_RETRY(mount(sourceDir.c_str(), targetDir.c_str(), nullptr,
+ MS_BIND | MS_REC, nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to %s: %s",
+ sourceDir.c_str(), targetDir.c_str(), strerror(errno)));
}
- if (TEMP_FAILURE_RETRY(mount(nullptr, targetDir.c_str(),
- nullptr, MS_SLAVE | MS_REC, nullptr)) == -1) {
- *error_msg = CREATE_ERROR("Failed to set MS_SLAVE for %s", targetDir.c_str());
- return false;
+
+ if (TEMP_FAILURE_RETRY(mount(nullptr, targetDir.c_str(), nullptr,
+ MS_SLAVE | MS_REC, nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to set MS_SLAVE for %s", targetDir.c_str()));
}
- return true;
}
-static bool mountPkgSpecificDir(const std::string& mntSourceRoot,
- const std::string& mntTargetRoot, const std::string& packageName,
- const char* dirName, std::string* error_msg) {
+static void MountPkgSpecificDir(const std::string& mntSourceRoot,
+ const std::string& mntTargetRoot,
+ const std::string& packageName,
+ const char* dirName,
+ fail_fn_t fail_fn) {
std::string mntSourceDir = StringPrintf("%s/Android/%s/%s",
mntSourceRoot.c_str(), dirName, packageName.c_str());
std::string mntTargetDir = StringPrintf("%s/Android/%s/%s",
mntTargetRoot.c_str(), dirName, packageName.c_str());
- return bindMount(mntSourceDir, mntTargetDir, error_msg);
+
+ BindMount(mntSourceDir, mntTargetDir, fail_fn);
}
-static bool preparePkgSpecificDirs(const std::vector<std::string>& packageNames,
- const std::vector<std::string>& volumeLabels, bool mountAllObbs,
- userid_t userId, std::string* error_msg) {
+static void PreparePkgSpecificDirs(const std::vector<std::string>& packageNames,
+ const std::vector<std::string>& volumeLabels,
+ bool mountAllObbs, userid_t userId, fail_fn_t fail_fn) {
for (auto& label : volumeLabels) {
std::string mntSource = StringPrintf("/mnt/runtime/write/%s", label.c_str());
std::string mntTarget = StringPrintf("/storage/%s", label.c_str());
@@ -501,28 +653,29 @@
StringAppendF(&mntSource, "/%d", userId);
StringAppendF(&mntTarget, "/%d", userId);
}
+
for (auto& package : packageNames) {
- mountPkgSpecificDir(mntSource, mntTarget, package, "data", error_msg);
- mountPkgSpecificDir(mntSource, mntTarget, package, "media", error_msg);
+ MountPkgSpecificDir(mntSource, mntTarget, package, "data", fail_fn);
+ MountPkgSpecificDir(mntSource, mntTarget, package, "media", fail_fn);
if (!mountAllObbs) {
- mountPkgSpecificDir(mntSource, mntTarget, package, "obb", error_msg);
+ MountPkgSpecificDir(mntSource, mntTarget, package, "obb", fail_fn);
}
}
+
if (mountAllObbs) {
StringAppendF(&mntSource, "/Android/obb");
StringAppendF(&mntTarget, "/Android/obb");
- bindMount(mntSource, mntTarget, error_msg);
+ BindMount(mntSource, mntTarget, fail_fn);
}
}
- return true;
}
// Create a private mount namespace and bind mount appropriate emulated
// storage for the given user.
-static bool MountEmulatedStorage(uid_t uid, jint mount_mode,
- bool force_mount_namespace, std::string* error_msg, const std::string& package_name,
+static void MountEmulatedStorage(uid_t uid, jint mount_mode,
+ bool force_mount_namespace, const std::string& package_name,
const std::vector<std::string>& packages_for_uid,
- const std::vector<std::string>& visible_vol_ids) {
+ const std::vector<std::string>& visible_vol_ids, fail_fn_t fail_fn) {
// See storage config details at http://source.android.com/tech/storage/
String8 storageSource;
@@ -534,18 +687,17 @@
storageSource = "/mnt/runtime/write";
} else if (mount_mode == MOUNT_EXTERNAL_NONE && !force_mount_namespace) {
// Sane default of no storage visible
- return true;
+ return;
}
// Create a second private mount namespace for our process
if (unshare(CLONE_NEWNS) == -1) {
- *error_msg = CREATE_ERROR("Failed to unshare(): %s", strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("Failed to unshare(): %s", strerror(errno)));
}
// Handle force_mount_namespace with MOUNT_EXTERNAL_NONE.
if (mount_mode == MOUNT_EXTERNAL_NONE) {
- return true;
+ return;
}
if (GetBoolProperty(kIsolatedStorageSnapshot, GetBoolProperty(kIsolatedStorage, false))) {
@@ -553,66 +705,64 @@
storageSource = (mount_mode == MOUNT_EXTERNAL_FULL)
? "/mnt/runtime/full" : "/mnt/runtime/write";
if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage",
- NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s",
- storageSource.string(),
- strerror(errno));
- return false;
+ NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",
+ storageSource.string(),
+ strerror(errno)));
}
// Mount user-specific symlink helper into place
userid_t user_id = multiuser_get_user_id(uid);
const String8 userSource(String8::format("/mnt/user/%d", user_id));
if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) {
- *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string());
- return false;
+ fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s (%s)",
+ userSource.string(), strerror(errno)));
}
- if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self",
- NULL, MS_BIND, NULL)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s",
- userSource.string(),
- strerror(errno));
- return false;
+
+ if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", nullptr, MS_BIND,
+ nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to /storage/self: %s",
+ userSource.string(),
+ strerror(errno)));
}
} else {
if (package_name.empty()) {
- return true;
+ return;
}
+
userid_t user_id = multiuser_get_user_id(uid);
- std::string pkgSandboxDir = StringPrintf("/mnt/user/%d/package/%s",
- user_id, package_name.c_str());
+ std::string pkgSandboxDir =
+ StringPrintf("/mnt/user/%d/package/%s", user_id, package_name.c_str());
struct stat sb;
bool sandboxAlreadyCreated = true;
if (TEMP_FAILURE_RETRY(lstat(pkgSandboxDir.c_str(), &sb)) == -1) {
if (errno == ENOENT) {
ALOGD("Sandbox not yet created for %s", pkgSandboxDir.c_str());
sandboxAlreadyCreated = false;
- if (!createPkgSandbox(uid, package_name, error_msg)) {
- return false;
- }
+ CreatePkgSandbox(uid, package_name, fail_fn);
} else {
- ALOGE("Failed to lstat %s", pkgSandboxDir.c_str());
- return false;
+ fail_fn(CREATE_ERROR("Failed to lstat %s: %s",
+ pkgSandboxDir.c_str(), strerror(errno)));
}
}
+
if (TEMP_FAILURE_RETRY(mount(pkgSandboxDir.c_str(), "/storage",
- nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s",
- pkgSandboxDir.c_str(), strerror(errno));
- return false;
+ nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",
+ pkgSandboxDir.c_str(), strerror(errno)));
}
+
if (access("/storage/obb_mount", F_OK) == 0) {
if (mount_mode != MOUNT_EXTERNAL_INSTALLER) {
remove("/storage/obb_mount");
}
} else {
if (mount_mode == MOUNT_EXTERNAL_INSTALLER) {
- int fd = TEMP_FAILURE_RETRY(open("/storage/obb_mount",
- O_RDWR | O_CREAT, 0660));
+ int fd =
+ TEMP_FAILURE_RETRY(open("/storage/obb_mount", O_RDWR | O_CREAT, 0660));
if (fd == -1) {
- *error_msg = CREATE_ERROR("Couldn't create /storage/obb_mount: %s",
- strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("Couldn't create /storage/obb_mount: %s",
+ strerror(errno)));
}
close(fd);
}
@@ -621,38 +771,32 @@
// pkg specific directories. Otherwise, leave as is and bind mounts will be taken
// care of by vold later.
if (sandboxAlreadyCreated) {
- if (!preparePkgSpecificDirs(packages_for_uid, visible_vol_ids,
- mount_mode == MOUNT_EXTERNAL_INSTALLER, user_id, error_msg)) {
- return false;
- }
+ PreparePkgSpecificDirs(packages_for_uid, visible_vol_ids,
+ mount_mode == MOUNT_EXTERNAL_INSTALLER, user_id, fail_fn);
}
}
} else {
- if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage",
- NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s",
- storageSource.string(),
- strerror(errno));
- return false;
+ if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", nullptr,
+ MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",
+ storageSource.string(),
+ strerror(errno)));
}
// Mount user-specific symlink helper into place
userid_t user_id = multiuser_get_user_id(uid);
const String8 userSource(String8::format("/mnt/user/%d", user_id));
if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) {
- *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string());
- return false;
+ fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s",
+ userSource.string()));
}
+
if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self",
- NULL, MS_BIND, NULL)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s",
- userSource.string(),
- strerror(errno));
- return false;
+ nullptr, MS_BIND, nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to /storage/self: %s",
+ userSource.string(), strerror(errno)));
}
}
-
- return true;
}
static bool NeedsNoRandomizeWorkaround() {
@@ -680,55 +824,45 @@
// descriptor (if any) is closed via dup2(), replacing it with a valid
// (open) descriptor to /dev/null.
-static bool DetachDescriptors(JNIEnv* env, jintArray fdsToClose, std::string* error_msg) {
- if (!fdsToClose) {
- return true;
- }
- jsize count = env->GetArrayLength(fdsToClose);
- ScopedIntArrayRO ar(env, fdsToClose);
- if (ar.get() == NULL) {
- *error_msg = "Bad fd array";
- return false;
- }
- jsize i;
- int devnull;
- for (i = 0; i < count; i++) {
- devnull = open("/dev/null", O_RDWR);
- if (devnull < 0) {
- *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno));
- return false;
+static void DetachDescriptors(JNIEnv* env,
+ const std::vector<int>& fds_to_close,
+ fail_fn_t fail_fn) {
+
+ if (fds_to_close.size() > 0) {
+ android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR));
+ if (devnull_fd == -1) {
+ fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
}
- ALOGV("Switching descriptor %d to /dev/null: %s", ar[i], strerror(errno));
- if (dup2(devnull, ar[i]) < 0) {
- *error_msg = StringPrintf("Failed dup2() on descriptor %d: %s", ar[i], strerror(errno));
- return false;
+
+ for (int fd : fds_to_close) {
+ ALOGV("Switching descriptor %d to /dev/null", fd);
+ if (dup2(devnull_fd, fd) == -1) {
+ fail_fn(StringPrintf("Failed dup2() on descriptor %d: %s", fd, strerror(errno)));
+ }
}
- close(devnull);
}
- return true;
}
-void SetThreadName(const char* thread_name) {
+void SetThreadName(const std::string& thread_name) {
bool hasAt = false;
bool hasDot = false;
- const char* s = thread_name;
- while (*s) {
- if (*s == '.') {
+
+ for (const char str_el : thread_name) {
+ if (str_el == '.') {
hasDot = true;
- } else if (*s == '@') {
+ } else if (str_el == '@') {
hasAt = true;
}
- s++;
}
- const int len = s - thread_name;
- if (len < 15 || hasAt || !hasDot) {
- s = thread_name;
- } else {
- s = thread_name + len - 15;
+
+ const char* name_start_ptr = thread_name.c_str();
+ if (thread_name.length() >= MAX_NAME_LENGTH && !hasAt && hasDot) {
+ name_start_ptr += thread_name.length() - MAX_NAME_LENGTH;
}
+
// pthread_setname_np fails rather than truncating long strings.
char buf[16]; // MAX_TASK_COMM_LEN=16 is hard-coded into bionic
- strlcpy(buf, s, sizeof(buf)-1);
+ strlcpy(buf, name_start_ptr, sizeof(buf) - 1);
errno = pthread_setname_np(pthread_self(), buf);
if (errno != 0) {
ALOGW("Unable to set the name of current thread to '%s': %s", buf, strerror(errno));
@@ -737,28 +871,16 @@
android::base::SetDefaultTag(buf);
}
-// The list of open zygote file descriptors.
-static FileDescriptorTable* gOpenFdTable = NULL;
-
-static bool FillFileDescriptorVector(JNIEnv* env,
- jintArray managed_fds,
- std::vector<int>* fds,
- std::string* error_msg) {
- CHECK(fds != nullptr);
- if (managed_fds != nullptr) {
- ScopedIntArrayRO ar(env, managed_fds);
- if (ar.get() == nullptr) {
- *error_msg = "Bad fd array";
- return false;
- }
- fds->reserve(ar.size());
- for (size_t i = 0; i < ar.size(); ++i) {
- fds->push_back(ar[i]);
- }
- }
- return true;
-}
-
+/**
+ * A failure function used to report fatal errors to the managed runtime. This
+ * function is often curried with the process name information and then passed
+ * to called functions.
+ *
+ * @param env Managed runtime environment
+ * @param process_name A native representation of the process name
+ * @param managed_process_name A managed representation of the process name
+ * @param msg The error message to be reported
+ */
[[noreturn]]
static void ZygoteFailure(JNIEnv* env,
const char* process_name,
@@ -779,12 +901,25 @@
__builtin_unreachable();
}
+/**
+ * A helper method for converting managed strings to native strings. A fatal
+ * error is generated if a problem is encountered in extracting a non-null
+ * string.
+ *
+ * @param env Managed runtime environment
+ * @param process_name A native representation of the process name
+ * @param managed_process_name A managed representation of the process name
+ * @param managed_string The managed string to extract
+ *
+ * @return An empty option if the managed string is null. A optional-wrapped
+ * string otherwise.
+ */
static std::optional<std::string> ExtractJString(JNIEnv* env,
const char* process_name,
jstring managed_process_name,
jstring managed_string) {
if (managed_string == nullptr) {
- return std::optional<std::string>();
+ return std::nullopt;
} else {
ScopedUtfChars scoped_string_chars(env, managed_string);
@@ -796,15 +931,124 @@
}
}
-// Utility routine to fork a zygote.
-static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
- jintArray managed_fds_to_close, jintArray managed_fds_to_ignore) {
- SetSignalHandlers();
+/**
+ * A helper method for converting managed string arrays to native vectors. A
+ * fatal error is generated if a problem is encountered in extracting a non-null array.
+ *
+ * @param env Managed runtime environment
+ * @param process_name A native representation of the process name
+ * @param managed_process_name A managed representation of the process name
+ * @param managed_array The managed integer array to extract
+ *
+ * @return An empty option if the managed array is null. A optional-wrapped
+ * vector otherwise.
+ */
+static std::optional<std::vector<int>> ExtractJIntArray(JNIEnv* env,
+ const char* process_name,
+ jstring managed_process_name,
+ jintArray managed_array) {
+ if (managed_array == nullptr) {
+ return std::nullopt;
+ } else {
+ ScopedIntArrayRO managed_array_handle(env, managed_array);
- // Block SIGCHLD prior to fork.
- sigset_t sigchld;
- sigemptyset(&sigchld);
- sigaddset(&sigchld, SIGCHLD);
+ if (managed_array_handle.get() != nullptr) {
+ std::vector<int> native_array;
+ native_array.reserve(managed_array_handle.size());
+
+ for (size_t array_index = 0; array_index < managed_array_handle.size(); ++array_index) {
+ native_array.push_back(managed_array_handle[array_index]);
+ }
+
+ return std::move(native_array);
+
+ } else {
+ ZygoteFailure(env, process_name, managed_process_name, "Failed to extract JIntArray.");
+ }
+ }
+}
+
+/**
+ * A helper method for converting managed string arrays to native vectors. A
+ * fatal error is generated if a problem is encountered in extracting a non-null array.
+ *
+ * @param env Managed runtime environment
+ * @param process_name A native representation of the process name
+ * @param managed_process_name A managed representation of the process name
+ * @param managed_array The managed string array to extract
+ *
+ * @return An empty option if the managed array is null. A optional-wrapped
+ * vector otherwise.
+ */
+static std::optional<std::vector<std::string>> ExtractJStringArray(JNIEnv* env,
+ const char* process_name,
+ jstring managed_process_name,
+ jobjectArray managed_array) {
+ if (managed_array == nullptr) {
+ return std::nullopt;
+ } else {
+ jsize element_count = env->GetArrayLength(managed_array);
+ std::vector<std::string> native_string_vector;
+ native_string_vector.reserve(element_count);
+
+ for (jsize array_index = 0; array_index < element_count; ++array_index) {
+ jstring managed_string = (jstring) env->GetObjectArrayElement(managed_array, array_index);
+ auto native_string = ExtractJString(env, process_name, managed_process_name, managed_string);
+
+ if (LIKELY(native_string.has_value())) {
+ native_string_vector.emplace_back(std::move(native_string.value()));
+ } else {
+ ZygoteFailure(env, process_name, managed_process_name,
+ "Null string found in managed string array.");
+ }
+ }
+
+ return std::move(native_string_vector);
+ }
+}
+
+/**
+ * A utility function for blocking signals.
+ *
+ * @param signum Signal number to block
+ * @param fail_fn Fatal error reporting function
+ *
+ * @see ZygoteFailure
+ */
+static void BlockSignal(int signum, fail_fn_t fail_fn) {
+ sigset_t sigs;
+ sigemptyset(&sigs);
+ sigaddset(&sigs, signum);
+
+ if (sigprocmask(SIG_BLOCK, &sigs, nullptr) == -1) {
+ fail_fn(CREATE_ERROR("Failed to block signal %s: %s", strsignal(signum), strerror(errno)));
+ }
+}
+
+
+/**
+ * A utility function for unblocking signals.
+ *
+ * @param signum Signal number to unblock
+ * @param fail_fn Fatal error reporting function
+ *
+ * @see ZygoteFailure
+ */
+static void UnblockSignal(int signum, fail_fn_t fail_fn) {
+ sigset_t sigs;
+ sigemptyset(&sigs);
+ sigaddset(&sigs, signum);
+
+ if (sigprocmask(SIG_UNBLOCK, &sigs, nullptr) == -1) {
+ fail_fn(CREATE_ERROR("Failed to un-block signal %s: %s", strsignal(signum), strerror(errno)));
+ }
+}
+
+// Utility routine to fork a process from the zygote.
+static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
+ const std::vector<int>& fds_to_close,
+ const std::vector<int>& fds_to_ignore) {
+ SetSignalHandlers();
// Curry a failure function.
auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote",
@@ -815,9 +1059,7 @@
// This would cause failures because the FDs are not whitelisted.
//
// Note that the zygote process is single threaded at this point.
- if (sigprocmask(SIG_BLOCK, &sigchld, nullptr) == -1) {
- fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)));
- }
+ BlockSignal(SIGCHLD, fail_fn);
// Close any logging related FDs before we start evaluating the list of
// file descriptors.
@@ -827,19 +1069,10 @@
// If this is the first fork for this zygote, create the open FD table. If
// it isn't, we just need to check whether the list of open files has changed
// (and it shouldn't in the normal case).
- std::string error_msg;
- std::vector<int> fds_to_ignore;
- if (!FillFileDescriptorVector(env, managed_fds_to_ignore, &fds_to_ignore, &error_msg)) {
- fail_fn(error_msg);
- }
-
if (gOpenFdTable == nullptr) {
- gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, &error_msg);
- if (gOpenFdTable == nullptr) {
- fail_fn(error_msg);
- }
- } else if (!gOpenFdTable->Restat(fds_to_ignore, &error_msg)) {
- fail_fn(error_msg);
+ gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, fail_fn);
+ } else {
+ gOpenFdTable->Restat(fds_to_ignore, fail_fn);
}
android_fdsan_error_level fdsan_error_level = android_fdsan_get_error_level();
@@ -851,24 +1084,18 @@
PreApplicationInit();
// Clean up any descriptors which must be closed immediately
- if (!DetachDescriptors(env, managed_fds_to_close, &error_msg)) {
- fail_fn(error_msg);
- }
+ DetachDescriptors(env, fds_to_close, fail_fn);
// Re-open all remaining open file descriptors so that they aren't shared
// with the zygote across a fork.
- if (!gOpenFdTable->ReopenOrDetach(&error_msg)) {
- fail_fn(error_msg);
- }
+ gOpenFdTable->ReopenOrDetach(fail_fn);
// Turn fdsan back on.
android_fdsan_set_error_level(fdsan_error_level);
}
// We blocked SIGCHLD prior to a fork, we unblock it here.
- if (sigprocmask(SIG_UNBLOCK, &sigchld, nullptr) == -1) {
- fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)));
- }
+ UnblockSignal(SIGCHLD, fail_fn);
return pid;
}
@@ -883,10 +1110,9 @@
jstring managed_app_data_dir, jstring managed_package_name,
jobjectArray managed_pacakges_for_uid,
jobjectArray managed_visible_vol_ids) {
- auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote",
- managed_nice_name, _1);
- auto extract_fn = std::bind(ExtractJString, env, is_system_server ? "system_server" : "zygote",
- managed_nice_name, _1);
+ const char* process_name = is_system_server ? "system_server" : "zygote";
+ auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
+ auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
auto se_info = extract_fn(managed_se_info);
auto nice_name = extract_fn(managed_nice_name);
@@ -894,22 +1120,14 @@
auto app_data_dir = extract_fn(managed_app_data_dir);
auto package_name = extract_fn(managed_package_name);
- std::string error_msg;
-
// Keep capabilities across UID change, unless we're staying root.
if (uid != 0) {
- if (!EnableKeepCapabilities(&error_msg)) {
- fail_fn(error_msg);
- }
+ EnableKeepCapabilities(fail_fn);
}
- if (!SetInheritable(permitted_capabilities, &error_msg)) {
- fail_fn(error_msg);
- }
+ SetInheritable(permitted_capabilities, fail_fn);
- if (!DropCapabilitiesBoundingSet(&error_msg)) {
- fail_fn(error_msg);
- }
+ DropCapabilitiesBoundingSet(fail_fn);
bool use_native_bridge = !is_system_server &&
instruction_set.has_value() &&
@@ -934,56 +1152,21 @@
}
}
- std::vector<std::string> packages_for_uid;
- if (managed_pacakges_for_uid != nullptr) {
- jsize count = env->GetArrayLength(managed_pacakges_for_uid);
- for (jsize package_index = 0; package_index < count; ++package_index) {
- jstring managed_package_for_uid =
- (jstring) env->GetObjectArrayElement(managed_pacakges_for_uid, package_index);
+ std::vector<std::string> packages_for_uid =
+ ExtractJStringArray(env, process_name, managed_nice_name, managed_pacakges_for_uid).
+ value_or(std::vector<std::string>());
- auto package_for_uid = extract_fn(managed_package_for_uid);
- if (LIKELY(package_for_uid.has_value())) {
- packages_for_uid.emplace_back(std::move(package_for_uid.value()));
- } else {
- fail_fn("Null string found in managed packages_for_uid.");
- }
- }
- }
+ std::vector<std::string> visible_vol_ids =
+ ExtractJStringArray(env, process_name, managed_nice_name, managed_visible_vol_ids).
+ value_or(std::vector<std::string>());
- std::vector<std::string> visible_vol_ids;
- if (managed_visible_vol_ids != nullptr) {
- jsize count = env->GetArrayLength(managed_visible_vol_ids);
- for (jsize vol_id_index = 0; vol_id_index < count; ++vol_id_index) {
- jstring managed_visible_vol_id =
- (jstring) env->GetObjectArrayElement(managed_visible_vol_ids, vol_id_index);
-
- auto visible_vol_id = extract_fn(managed_visible_vol_id);
- if (LIKELY(visible_vol_id.has_value())) {
- visible_vol_ids.emplace_back(std::move(visible_vol_id.value()));
- } else {
- fail_fn("Null string found in managed visible_vol_ids.");
- }
- }
- }
-
- if (!MountEmulatedStorage(uid, mount_external, use_native_bridge, &error_msg,
- package_name.value(), packages_for_uid, visible_vol_ids)) {
- ALOGW("Failed to mount emulated storage: %s (%s)", error_msg.c_str(), strerror(errno));
- if (errno == ENOTCONN || errno == EROFS) {
- // When device is actively encrypting, we get ENOTCONN here
- // since FUSE was mounted before the framework restarted.
- // When encrypted device is booting, we get EROFS since
- // FUSE hasn't been created yet by init.
- // In either case, continue without external storage.
- } else {
- fail_fn(error_msg);
- }
- }
+ MountEmulatedStorage(uid, mount_external, use_native_bridge, package_name.value(),
+ packages_for_uid, visible_vol_ids, fail_fn);
// If this zygote isn't root, it won't be able to create a process group,
// since the directory is owned by root.
if (!is_system_server && getuid() == 0) {
- int rc = createProcessGroup(uid, getpid());
+ const int rc = createProcessGroup(uid, getpid());
if (rc == -EROFS) {
ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?");
} else if (rc != 0) {
@@ -991,13 +1174,8 @@
}
}
- if (!SetGids(env, gids, &error_msg)) {
- fail_fn(error_msg);
- }
-
- if (!SetRLimits(env, rlimits, &error_msg)) {
- fail_fn(error_msg);
- }
+ SetGids(env, gids, fail_fn);
+ SetRLimits(env, rlimits, fail_fn);
if (use_native_bridge) {
// Due to the logic behind use_native_bridge we know that both app_data_dir
@@ -1056,14 +1234,9 @@
}
}
- if (!SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities,
- &error_msg)) {
- fail_fn(error_msg);
- }
+ SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, fail_fn);
- if (!SetSchedulerPolicy(&error_msg)) {
- fail_fn(error_msg);
- }
+ SetSchedulerPolicy(fail_fn);
const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr;
const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr;
@@ -1076,7 +1249,7 @@
// Make it easier to debug audit logs by setting the main thread's name to the
// nice name rather than "app_process".
if (nice_name.has_value()) {
- SetThreadName(nice_name.value().c_str());
+ SetThreadName(nice_name.value());
} else if (is_system_server) {
SetThreadName("system_server");
}
@@ -1089,6 +1262,7 @@
if (env->ExceptionCheck()) {
fail_fn("Error calling post fork system server hooks.");
}
+
// TODO(oth): Remove hardcoded label here (b/117874058).
static const char* kSystemServerLabel = "u:r:system_server:s0";
if (selinux_android_setcon(kSystemServerLabel) != 0) {
@@ -1192,6 +1366,74 @@
return capabilities & GetEffectiveCapabilityMask(env);
}
+
+/**
+ * Adds the given information about a newly created blastula to the Zygote's
+ * blastula table.
+ *
+ * @param blastula_pid Process ID of the newly created blastula
+ * @param read_pipe_fd File descriptor for the read end of the blastula
+ * reporting pipe. Used in the ZygoteServer poll loop to track blastula
+ * specialization.
+ */
+static void AddBlastulaTableEntry(pid_t blastula_pid, int read_pipe_fd) {
+ static int sBlastulaTableInsertIndex = 0;
+
+ int search_index = sBlastulaTableInsertIndex;
+
+ do {
+ if (gBlastulaTable[search_index].SetIfInvalid(blastula_pid, read_pipe_fd)) {
+ // Start our next search right after where we finished this one.
+ sBlastulaTableInsertIndex = (search_index + 1) % gBlastulaTable.size();
+
+ return;
+ }
+
+ search_index = (search_index + 1) % gBlastulaTable.size();
+ } while (search_index != sBlastulaTableInsertIndex);
+
+ // Much like money in the banana stand, there should always be an entry
+ // in the blastula table.
+ __builtin_unreachable();
+}
+
+/**
+ * Invalidates the entry in the BlastulaTable corresponding to the provided
+ * process ID if it is present. If an entry was removed the blastula pool
+ * count is decremented.
+ *
+ * @param blastula_pid Process ID of the blastula entry to invalidate
+ * @return True if an entry was invalidated; false otherwise
+ */
+static bool RemoveBlastulaTableEntry(pid_t blastula_pid) {
+ for (BlastulaTableEntry& entry : gBlastulaTable) {
+ if (entry.ClearForPID(blastula_pid)) {
+ --gBlastulaPoolCount;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * @return A vector of the read pipe FDs for each of the active blastulas.
+ */
+std::vector<int> MakeBlastulaPipeReadFDVector() {
+ std::vector<int> fd_vec;
+ fd_vec.reserve(gBlastulaTable.size());
+
+ for (BlastulaTableEntry& entry : gBlastulaTable) {
+ auto entry_values = entry.GetValues();
+
+ if (entry_values.has_value()) {
+ fd_vec.push_back(entry_values.value().read_pipe_fd);
+ }
+ }
+
+ return fd_vec;
+}
+
} // anonymous namespace
namespace android {
@@ -1210,12 +1452,35 @@
JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits,
jint mount_external, jstring se_info, jstring nice_name,
- jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
+ jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
jstring instruction_set, jstring app_data_dir, jstring package_name,
jobjectArray packages_for_uid, jobjectArray visible_vol_ids) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+ if (UNLIKELY(managed_fds_to_close == nullptr)) {
+ ZygoteFailure(env, "zygote", nice_name, "Zygote received a null fds_to_close vector.");
+ }
+
+ std::vector<int> fds_to_close =
+ ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_close).value();
+ std::vector<int> fds_to_ignore =
+ ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_ignore)
+ .value_or(std::vector<int>());
+
+ std::vector<int> blastula_pipes = MakeBlastulaPipeReadFDVector();
+
+ fds_to_close.insert(fds_to_close.end(), blastula_pipes.begin(), blastula_pipes.end());
+ fds_to_ignore.insert(fds_to_ignore.end(), blastula_pipes.begin(), blastula_pipes.end());
+
+ fds_to_close.push_back(gBlastulaPoolSocketFD);
+
+ if (gBlastulaPoolEventFD != -1) {
+ fds_to_close.push_back(gBlastulaPoolEventFD);
+ fds_to_ignore.push_back(gBlastulaPoolEventFD);
+ }
+
pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore);
+
if (pid == 0) {
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
capabilities, capabilities,
@@ -1230,9 +1495,19 @@
JNIEnv* env, jclass, uid_t uid, gid_t gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities,
jlong effective_capabilities) {
+ std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()),
+ fds_to_ignore(fds_to_close);
+
+ fds_to_close.push_back(gBlastulaPoolSocketFD);
+
+ if (gBlastulaPoolEventFD != -1) {
+ fds_to_close.push_back(gBlastulaPoolEventFD);
+ fds_to_ignore.push_back(gBlastulaPoolEventFD);
+ }
+
pid_t pid = ForkCommon(env, true,
- /* managed_fds_to_close= */ nullptr,
- /* managed_fds_to_ignore= */ nullptr);
+ fds_to_close,
+ fds_to_ignore);
if (pid == 0) {
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
permitted_capabilities, effective_capabilities,
@@ -1266,6 +1541,52 @@
return pid;
}
+/**
+ * A JNI function that forks a blastula from the Zygote while ensuring proper
+ * file descriptor hygiene.
+ *
+ * @param env Managed runtime environment
+ * @param read_pipe_fd The read FD for the blastula reporting pipe. Manually closed by blastlas
+ * in managed code.
+ * @param write_pipe_fd The write FD for the blastula reporting pipe. Manually closed by the
+ * zygote in managed code.
+ * @param managed_session_socket_fds A list of anonymous session sockets that must be ignored by
+ * the FD hygiene code and automatically "closed" in the new blastula.
+ * @return
+ */
+static jint com_android_internal_os_Zygote_nativeForkBlastula(JNIEnv* env, jclass,
+ jint read_pipe_fd, jint write_pipe_fd, jintArray managed_session_socket_fds) {
+ std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()),
+ fds_to_ignore(fds_to_close);
+
+ std::vector<int> session_socket_fds =
+ ExtractJIntArray(env, "blastula", nullptr, managed_session_socket_fds)
+ .value_or(std::vector<int>());
+
+ // The Blastula Pool Event FD is created during the initialization of the
+ // blastula pool and should always be valid here.
+
+ fds_to_close.push_back(gZygoteSocketFD);
+ fds_to_close.push_back(gBlastulaPoolEventFD);
+ fds_to_close.insert(fds_to_close.end(), session_socket_fds.begin(), session_socket_fds.end());
+
+ fds_to_ignore.push_back(gZygoteSocketFD);
+ fds_to_ignore.push_back(gBlastulaPoolSocketFD);
+ fds_to_ignore.push_back(gBlastulaPoolEventFD);
+ fds_to_ignore.push_back(read_pipe_fd);
+ fds_to_ignore.push_back(write_pipe_fd);
+ fds_to_ignore.insert(fds_to_ignore.end(), session_socket_fds.begin(), session_socket_fds.end());
+
+ pid_t blastula_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore);
+
+ if (blastula_pid != 0) {
+ ++gBlastulaPoolCount;
+ AddBlastulaTableEntry(blastula_pid, read_pipe_fd);
+ }
+
+ return blastula_pid;
+}
+
static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork(
JNIEnv* env, jclass, jstring path) {
ScopedUtfChars path_native(env, path);
@@ -1321,14 +1642,125 @@
return;
}
- // TODO(b/111434506) install the filter
-
- /*
bool installed = install_setuidgid_seccomp_filter(uidGidMin, uidGidMax);
if (!installed) {
RuntimeAbort(env, __LINE__, "Could not install setuid/setgid seccomp filter.");
}
- */
+}
+
+/**
+ * Called from a blastula to specialize the process for a specific application.
+ *
+ * @param env Managed runtime environment
+ * @param uid User ID of the new application
+ * @param gid Group ID of the new application
+ * @param gids Extra groups that the process belongs to
+ * @param runtime_flags Flags for changing the behavior of the managed runtime
+ * @param rlimits Resource limits
+ * @param mount_external The mode (read/write/normal) that external storage will be mounted with
+ * @param se_info SELinux policy information
+ * @param nice_name New name for this process
+ * @param is_child_zygote If the process is to become a WebViewZygote
+ * @param instruction_set The instruction set expected/requested by the new application
+ * @param app_data_dir Path to the application's data directory
+ */
+static void com_android_internal_os_Zygote_nativeSpecializeBlastula(
+ JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
+ jint runtime_flags, jobjectArray rlimits,
+ jint mount_external, jstring se_info, jstring nice_name,
+ jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir,
+ jstring package_name, jobjectArray packages_for_uid, jobjectArray visible_vol_ids) {
+ jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+
+ SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
+ capabilities, capabilities,
+ mount_external, se_info, nice_name, false,
+ is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+ package_name, packages_for_uid, visible_vol_ids);
+}
+
+/**
+ * A helper method for fetching socket file descriptors that were opened by init from the
+ * environment.
+ *
+ * @param env Managed runtime environment
+ * @param is_primary If this process is the primary or secondary Zygote; used to compute the name
+ * of the environment variable storing the file descriptors.
+ */
+static void com_android_internal_os_Zygote_nativeGetSocketFDs(JNIEnv* env, jclass,
+ jboolean is_primary) {
+ std::string android_socket_prefix(ANDROID_SOCKET_PREFIX);
+ std::string env_var_name = android_socket_prefix + (is_primary ? "zygote" : "zygote_secondary");
+ char* env_var_val = getenv(env_var_name.c_str());
+
+ if (env_var_val != nullptr) {
+ gZygoteSocketFD = atoi(env_var_val);
+ ALOGV("Zygote:zygoteSocketFD = %d", gZygoteSocketFD);
+ } else {
+ ALOGE("Unable to fetch Zygote socket file descriptor");
+ }
+
+ env_var_name = android_socket_prefix + (is_primary ? "blastula_pool" : "blastula_pool_secondary");
+ env_var_val = getenv(env_var_name.c_str());
+
+ if (env_var_val != nullptr) {
+ gBlastulaPoolSocketFD = atoi(env_var_val);
+ ALOGV("Zygote:blastulaPoolSocketFD = %d", gBlastulaPoolSocketFD);
+ } else {
+ ALOGE("Unable to fetch Blastula pool socket file descriptor");
+ }
+}
+
+/**
+ * @param env Managed runtime environment
+ * @return A managed array of raw file descriptors for the read ends of the blastula reporting
+ * pipes.
+ */
+static jintArray com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs(JNIEnv* env, jclass) {
+ std::vector<int> blastula_fds = MakeBlastulaPipeReadFDVector();
+
+ jintArray managed_blastula_fds = env->NewIntArray(blastula_fds.size());
+ env->SetIntArrayRegion(managed_blastula_fds, 0, blastula_fds.size(), blastula_fds.data());
+
+ return managed_blastula_fds;
+}
+
+/**
+ * A JNI wrapper around RemoveBlastulaTableEntry.
+ *
+ * @param env Managed runtime environment
+ * @param blastula_pid Process ID of the blastula entry to invalidate
+ * @return True if an entry was invalidated; false otherwise.
+ */
+static jboolean com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry(JNIEnv* env, jclass,
+ jint blastula_pid) {
+ return RemoveBlastulaTableEntry(blastula_pid);
+}
+
+/**
+ * Creates the blastula pool event FD if it doesn't exist and returns it. This is used by the
+ * ZygoteServer poll loop to know when to re-fill the blastula pool.
+ *
+ * @param env Managed runtime environment
+ * @return A raw event file descriptor used to communicate (from the signal handler) when the
+ * Zygote receives a SIGCHLD for a blastula
+ */
+static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD(JNIEnv* env, jclass) {
+ if (gBlastulaPoolEventFD == -1) {
+ if ((gBlastulaPoolEventFD = eventfd(0, 0)) == -1) {
+ ZygoteFailure(env, "zygote", nullptr, StringPrintf("Unable to create eventfd: %s", strerror(errno)));
+ }
+ }
+
+ return gBlastulaPoolEventFD;
+}
+
+/**
+ * @param env Managed runtime environment
+ * @return The number of blastulas currently in the blastula pool
+ */
+static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolCount(JNIEnv* env, jclass) {
+ return gBlastulaPoolCount;
}
static const JNINativeMethod gMethods[] = {
@@ -1346,7 +1778,22 @@
{ "nativePreApplicationInit", "()V",
(void *) com_android_internal_os_Zygote_nativePreApplicationInit },
{ "nativeInstallSeccompUidGidFilter", "(II)V",
- (void *) com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter }
+ (void *) com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter },
+ { "nativeForkBlastula", "(II[I)I",
+ (void *) com_android_internal_os_Zygote_nativeForkBlastula },
+ { "nativeSpecializeBlastula",
+ "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
+ (void *) com_android_internal_os_Zygote_nativeSpecializeBlastula },
+ { "nativeGetSocketFDs", "(Z)V",
+ (void *) com_android_internal_os_Zygote_nativeGetSocketFDs },
+ { "nativeGetBlastulaPipeFDs", "()[I",
+ (void *) com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs },
+ { "nativeRemoveBlastulaTableEntry", "(I)Z",
+ (void *) com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry },
+ { "nativeGetBlastulaPoolEventFD", "()I",
+ (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD },
+ { "nativeGetBlastulaPoolCount", "()I",
+ (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolCount }
};
int register_com_android_internal_os_Zygote(JNIEnv* env) {
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index d60d1a6..4e48663 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -38,6 +38,8 @@
"/dev/null",
"/dev/socket/zygote",
"/dev/socket/zygote_secondary",
+ "/dev/socket/blastula_pool",
+ "/dev/socket/blastula_pool_secondary",
"/dev/socket/webview_zygote",
"/dev/socket/heapprofd",
"/sys/kernel/debug/tracing/trace_marker",
@@ -70,6 +72,7 @@
return true;
}
+ // Framework jars are allowed.
static const char* kFrameworksPrefix = "/system/framework/";
static const char* kJarSuffix = ".jar";
if (android::base::StartsWith(path, kFrameworksPrefix)
@@ -77,6 +80,13 @@
return true;
}
+ // Jars from the runtime apex are allowed.
+ static const char* kRuntimeApexPrefix = "/apex/com.android.runtime/javalib/";
+ if (android::base::StartsWith(path, kRuntimeApexPrefix)
+ && android::base::EndsWith(path, kJarSuffix)) {
+ return true;
+ }
+
// Whitelist files needed for Runtime Resource Overlay, like these:
// /system/vendor/overlay/framework-res.apk
// /system/vendor/overlay-subdir/pg/framework-res.apk
@@ -134,15 +144,14 @@
// open zygote file descriptor.
class FileDescriptorInfo {
public:
- // Create a FileDescriptorInfo for a given file descriptor. Returns
- // |NULL| if an error occurred.
- static FileDescriptorInfo* CreateFromFd(int fd, std::string* error_msg);
+ // Create a FileDescriptorInfo for a given file descriptor.
+ static FileDescriptorInfo* CreateFromFd(int fd, fail_fn_t fail_fn);
// Checks whether the file descriptor associated with this object
// refers to the same description.
- bool Restat() const;
+ bool RefersToSameFile() const;
- bool ReopenOrDetach(std::string* error_msg) const;
+ void ReopenOrDetach(fail_fn_t fail_fn) const;
const int fd;
const struct stat stat;
@@ -168,19 +177,18 @@
// address).
static bool GetSocketName(const int fd, std::string* result);
- bool DetachSocket(std::string* error_msg) const;
+ void DetachSocket(fail_fn_t fail_fn) const;
DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo);
};
// static
-FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_msg) {
+FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) {
struct stat f_stat;
// This should never happen; the zygote should always have the right set
// of permissions required to stat all its open files.
if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
- *error_msg = android::base::StringPrintf("Unable to stat %d", fd);
- return nullptr;
+ fail_fn(android::base::StringPrintf("Unable to stat %d", fd));
}
const FileDescriptorWhitelist* whitelist = FileDescriptorWhitelist::Get();
@@ -188,15 +196,13 @@
if (S_ISSOCK(f_stat.st_mode)) {
std::string socket_name;
if (!GetSocketName(fd, &socket_name)) {
- *error_msg = "Unable to get socket name";
- return nullptr;
+ fail_fn("Unable to get socket name");
}
if (!whitelist->IsAllowed(socket_name)) {
- *error_msg = android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)",
- socket_name.c_str(),
- fd);
- return nullptr;
+ fail_fn(android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)",
+ socket_name.c_str(),
+ fd));
}
return new FileDescriptorInfo(fd);
@@ -209,26 +215,35 @@
// S_ISDIR : Not supported. (We could if we wanted to, but it's unused).
// S_ISLINK : Not supported.
// S_ISBLK : Not supported.
- // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate
- // with the child process across forks but those should have been closed
- // before we got to this point.
+ // S_ISFIFO : Not supported. Note that the Zygote and blastulas use pipes to
+ // communicate with the child processes across forks but those should have been
+ // added to the redirection exemption list.
if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) {
- *error_msg = android::base::StringPrintf("Unsupported st_mode %u", f_stat.st_mode);
- return nullptr;
+ std::string mode = "Unknown";
+
+ if (S_ISDIR(f_stat.st_mode)) {
+ mode = "DIR";
+ } else if (S_ISLNK(f_stat.st_mode)) {
+ mode = "LINK";
+ } else if (S_ISBLK(f_stat.st_mode)) {
+ mode = "BLOCK";
+ } else if (S_ISFIFO(f_stat.st_mode)) {
+ mode = "FIFO";
+ }
+
+ fail_fn(android::base::StringPrintf("Unsupported st_mode for FD %d: %s", fd, mode.c_str()));
}
std::string file_path;
const std::string fd_path = android::base::StringPrintf("/proc/self/fd/%d", fd);
if (!android::base::Readlink(fd_path, &file_path)) {
- *error_msg = android::base::StringPrintf("Could not read fd link %s: %s",
- fd_path.c_str(),
- strerror(errno));
- return nullptr;
+ fail_fn(android::base::StringPrintf("Could not read fd link %s: %s",
+ fd_path.c_str(),
+ strerror(errno)));
}
if (!whitelist->IsAllowed(file_path)) {
- *error_msg = std::string("Not whitelisted : ").append(file_path);
- return nullptr;
+ fail_fn(std::string("Not whitelisted : ").append(file_path));
}
// File descriptor flags : currently on FD_CLOEXEC. We can set these
@@ -236,11 +251,10 @@
// there won't be any races.
const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD));
if (fd_flags == -1) {
- *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s",
- fd,
- file_path.c_str(),
- strerror(errno));
- return nullptr;
+ fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s",
+ fd,
+ file_path.c_str(),
+ strerror(errno)));
}
// File status flags :
@@ -257,11 +271,10 @@
// their presence and pass them in to open().
int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
if (fs_flags == -1) {
- *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s",
- fd,
- file_path.c_str(),
- strerror(errno));
- return nullptr;
+ fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s",
+ fd,
+ file_path.c_str(),
+ strerror(errno)));
}
// File offset : Ignore the offset for non seekable files.
@@ -276,7 +289,7 @@
return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset);
}
-bool FileDescriptorInfo::Restat() const {
+bool FileDescriptorInfo::RefersToSameFile() const {
struct stat f_stat;
if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
PLOG(ERROR) << "Unable to restat fd " << fd;
@@ -286,9 +299,9 @@
return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev;
}
-bool FileDescriptorInfo::ReopenOrDetach(std::string* error_msg) const {
+void FileDescriptorInfo::ReopenOrDetach(fail_fn_t fail_fn) const {
if (is_sock) {
- return DetachSocket(error_msg);
+ return DetachSocket(fail_fn);
}
// NOTE: This might happen if the file was unlinked after being opened.
@@ -297,57 +310,50 @@
const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags));
if (new_fd == -1) {
- *error_msg = android::base::StringPrintf("Failed open(%s, %i): %s",
- file_path.c_str(),
- open_flags,
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed open(%s, %i): %s",
+ file_path.c_str(),
+ open_flags,
+ strerror(errno)));
}
if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) {
close(new_fd);
- *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s",
- new_fd,
- fd_flags,
- file_path.c_str(),
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s",
+ new_fd,
+ fd_flags,
+ file_path.c_str(),
+ strerror(errno)));
}
if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) {
close(new_fd);
- *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s",
- new_fd,
- fs_flags,
- file_path.c_str(),
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s",
+ new_fd,
+ fs_flags,
+ file_path.c_str(),
+ strerror(errno)));
}
if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) {
close(new_fd);
- *error_msg = android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s",
- new_fd,
- file_path.c_str(),
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s",
+ new_fd,
+ file_path.c_str(),
+ strerror(errno)));
}
- int dupFlags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0;
- if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dupFlags)) == -1) {
+ int dup_flags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0;
+ if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dup_flags)) == -1) {
close(new_fd);
- *error_msg = android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s",
- fd,
- new_fd,
- dupFlags,
- file_path.c_str(),
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s",
+ fd,
+ new_fd,
+ dup_flags,
+ file_path.c_str(),
+ strerror(errno)));
}
close(new_fd);
-
- return true;
}
FileDescriptorInfo::FileDescriptorInfo(int fd) :
@@ -373,7 +379,6 @@
is_sock(false) {
}
-// static
bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) {
sockaddr_storage ss;
sockaddr* addr = reinterpret_cast<sockaddr*>(&ss);
@@ -417,86 +422,75 @@
return true;
}
-bool FileDescriptorInfo::DetachSocket(std::string* error_msg) const {
+void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const {
const int dev_null_fd = open("/dev/null", O_RDWR);
if (dev_null_fd < 0) {
- *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno));
- return false;
+ fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
}
if (dup2(dev_null_fd, fd) == -1) {
- *error_msg = android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s",
- fd,
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s",
+ fd,
+ strerror(errno)));
}
if (close(dev_null_fd) == -1) {
- *error_msg = android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno)));
}
-
- return true;
}
// static
FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore,
- std::string* error_msg) {
- DIR* d = opendir(kFdPath);
- if (d == nullptr) {
- *error_msg = std::string("Unable to open directory ").append(kFdPath);
- return nullptr;
+ fail_fn_t fail_fn) {
+ DIR* proc_fd_dir = opendir(kFdPath);
+ if (proc_fd_dir == nullptr) {
+ fail_fn(std::string("Unable to open directory ").append(kFdPath));
}
- int dir_fd = dirfd(d);
- dirent* e;
+
+ int dir_fd = dirfd(proc_fd_dir);
+ dirent* dir_entry;
std::unordered_map<int, FileDescriptorInfo*> open_fd_map;
- while ((e = readdir(d)) != NULL) {
- const int fd = ParseFd(e, dir_fd);
+ while ((dir_entry = readdir(proc_fd_dir)) != nullptr) {
+ const int fd = ParseFd(dir_entry, dir_fd);
if (fd == -1) {
continue;
}
+
if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
LOG(INFO) << "Ignoring open file descriptor " << fd;
continue;
}
- FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg);
- if (info == NULL) {
- if (closedir(d) == -1) {
- PLOG(ERROR) << "Unable to close directory";
- }
- return NULL;
- }
- open_fd_map[fd] = info;
+ open_fd_map[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
}
- if (closedir(d) == -1) {
- *error_msg = "Unable to close directory";
- return nullptr;
+ if (closedir(proc_fd_dir) == -1) {
+ fail_fn("Unable to close directory");
}
+
return new FileDescriptorTable(open_fd_map);
}
-bool FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg) {
+void FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn) {
std::set<int> open_fds;
// First get the list of open descriptors.
- DIR* d = opendir(kFdPath);
- if (d == NULL) {
- *error_msg = android::base::StringPrintf("Unable to open directory %s: %s",
- kFdPath,
- strerror(errno));
- return false;
+ DIR* proc_fd_dir = opendir(kFdPath);
+ if (proc_fd_dir == nullptr) {
+ fail_fn(android::base::StringPrintf("Unable to open directory %s: %s",
+ kFdPath,
+ strerror(errno)));
}
- int dir_fd = dirfd(d);
- dirent* e;
- while ((e = readdir(d)) != NULL) {
- const int fd = ParseFd(e, dir_fd);
+ int dir_fd = dirfd(proc_fd_dir);
+ dirent* dir_entry;
+ while ((dir_entry = readdir(proc_fd_dir)) != nullptr) {
+ const int fd = ParseFd(dir_entry, dir_fd);
if (fd == -1) {
continue;
}
+
if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
LOG(INFO) << "Ignoring open file descriptor " << fd;
continue;
@@ -505,27 +499,24 @@
open_fds.insert(fd);
}
- if (closedir(d) == -1) {
- *error_msg = android::base::StringPrintf("Unable to close directory: %s", strerror(errno));
- return false;
+ if (closedir(proc_fd_dir) == -1) {
+ fail_fn(android::base::StringPrintf("Unable to close directory: %s", strerror(errno)));
}
- return RestatInternal(open_fds, error_msg);
+ RestatInternal(open_fds, fail_fn);
}
-// Reopens all file descriptors that are contained in the table. Returns true
-// if all descriptors were successfully re-opened or detached, and false if an
-// error occurred.
-bool FileDescriptorTable::ReopenOrDetach(std::string* error_msg) {
+// Reopens all file descriptors that are contained in the table.
+void FileDescriptorTable::ReopenOrDetach(fail_fn_t fail_fn) {
std::unordered_map<int, FileDescriptorInfo*>::const_iterator it;
for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) {
const FileDescriptorInfo* info = it->second;
- if (info == NULL || !info->ReopenOrDetach(error_msg)) {
- return false;
+ if (info == nullptr) {
+ return;
+ } else {
+ info->ReopenOrDetach(fail_fn);
}
}
-
- return true;
}
FileDescriptorTable::FileDescriptorTable(
@@ -533,9 +524,7 @@
: open_fd_map_(map) {
}
-bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds, std::string* error_msg) {
- bool error = false;
-
+void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn) {
// Iterate through the list of file descriptors we've already recorded
// and check whether :
//
@@ -558,28 +547,18 @@
} else {
// The entry from the file descriptor table is still open. Restat
// it and check whether it refers to the same file.
- const bool same_file = it->second->Restat();
- if (!same_file) {
+ if (!it->second->RefersToSameFile()) {
// The file descriptor refers to a different description. We must
// update our entry in the table.
delete it->second;
- it->second = FileDescriptorInfo::CreateFromFd(*element, error_msg);
- if (it->second == NULL) {
- // The descriptor no longer no longer refers to a whitelisted file.
- // We flag an error and remove it from the list of files we're
- // tracking.
- error = true;
- it = open_fd_map_.erase(it);
- } else {
- // Successfully restatted the file, move on to the next open FD.
- ++it;
- }
+ it->second = FileDescriptorInfo::CreateFromFd(*element, fail_fn);
} else {
// It's the same file. Nothing to do here. Move on to the next open
// FD.
- ++it;
}
+ ++it;
+
// Finally, remove the FD from the set of open_fds. We do this last because
// |element| will not remain valid after a call to erase.
open_fds.erase(element);
@@ -598,25 +577,15 @@
std::set<int>::const_iterator it;
for (it = open_fds.begin(); it != open_fds.end(); ++it) {
const int fd = (*it);
- FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg);
- if (info == NULL) {
- // A newly opened file is not on the whitelist. Flag an error and
- // continue.
- error = true;
- } else {
- // Track the newly opened file.
- open_fd_map_[fd] = info;
- }
+ open_fd_map_[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
}
}
-
- return !error;
}
// static
-int FileDescriptorTable::ParseFd(dirent* e, int dir_fd) {
+int FileDescriptorTable::ParseFd(dirent* dir_entry, int dir_fd) {
char* end;
- const int fd = strtol(e->d_name, &end, 10);
+ const int fd = strtol(dir_entry->d_name, &end, 10);
if ((*end) != '\0') {
return -1;
}
diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h
index 09022a2..2caf157 100644
--- a/core/jni/fd_utils.h
+++ b/core/jni/fd_utils.h
@@ -30,6 +30,9 @@
class FileDescriptorInfo;
+// This type is duplicated in com_android_internal_os_Zygote.cpp
+typedef const std::function<void(std::string)>& fail_fn_t;
+
// Whitelist of open paths that the zygote is allowed to keep open.
//
// In addition to the paths listed in kPathWhitelist in file_utils.cpp, and
@@ -76,19 +79,19 @@
// /proc/self/fd for the list of open file descriptors and collects
// information about them. Returns NULL if an error occurs.
static FileDescriptorTable* Create(const std::vector<int>& fds_to_ignore,
- std::string* error_msg);
+ fail_fn_t fail_fn);
- bool Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg);
+ void Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn);
// Reopens all file descriptors that are contained in the table. Returns true
// if all descriptors were successfully re-opened or detached, and false if an
// error occurred.
- bool ReopenOrDetach(std::string* error_msg);
+ void ReopenOrDetach(fail_fn_t fail_fn);
private:
explicit FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map);
- bool RestatInternal(std::set<int>& open_fds, std::string* error_msg);
+ void RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn);
static int ParseFd(dirent* e, int dir_fd);
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index f68c760..eb716ac 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2179,4 +2179,14 @@
// OPEN: Settings > Network & internet > Click Mobile network to land on a page with a list of
// SIM/eSIM subscriptions.
MOBILE_NETWORK_LIST = 1627;
+
+ // OPEN: Settings > Display > Adaptive sleep
+ // OS: Q
+ SETTINGS_ADAPTIVE_SLEEP = 1628;
+
+ // OPEN: Settings > System > Aware
+ SETTINGS_AWARE = 1632;
+
+ // OPEN: Settings > System > Aware > Disable > Dialog
+ DIALOG_AWARE_DISABLE = 1633;
}
diff --git a/core/proto/android/debug/enums.proto b/core/proto/android/debug/enums.proto
new file mode 100644
index 0000000..6747bb7
--- /dev/null
+++ b/core/proto/android/debug/enums.proto
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package android.debug;
+
+option java_outer_classname = "AdbProtoEnums";
+option java_multiple_files = true;
+
+/**
+ * adb connection state used to track adb connection changes in AdbDebuggingManager.java.
+ */
+enum AdbConnectionStateEnum {
+ UNKNOWN = 0;
+
+ /**
+ * The adb connection is waiting for approval from the user.
+ */
+ AWAITING_USER_APPROVAL = 1;
+
+ /**
+ * The user allowed the adb connection from the system.
+ */
+ USER_ALLOWED = 2;
+
+ /**
+ * The user denied the adb connection from the system.
+ */
+ USER_DENIED = 3;
+
+ /**
+ * The adb connection was automatically allowed without user interaction due to the system
+ * being previously allowed by the user with the 'always allow' option selected, and the adb
+ * grant has not yet expired.
+ */
+ AUTOMATICALLY_ALLOWED = 4;
+
+ /**
+ * An empty or invalid base64 encoded key was provided to the framework; the connection was
+ * automatically denied.
+ */
+ DENIED_INVALID_KEY = 5;
+
+ /**
+ * vold decrypt has not yet occurred; the connection was automatically denied.
+ */
+ DENIED_VOLD_DECRYPT = 6;
+
+ /**
+ * The adb session has been disconnected.
+ */
+ DISCONNECTED = 7;
+}
+
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 4158577..d79eb94 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -449,6 +449,8 @@
optional SettingProto gup_dev_opt_out_apps = 10;
// GUP - List of Apps that are forbidden to use Game Update Package
optional SettingProto gup_blacklist = 11;
+ // List of Apps that are allowed to use Game Driver package.
+ optional SettingProto game_driver_whitelist = 12;
}
optional Gpu gpu = 59;
@@ -535,6 +537,16 @@
// Whether automatic battery saver mode is controlled via percentage,
// {@link #DYNAMIC_POWER_SAVINGS_ENABLED} or disabled.
optional SettingProto automatic_power_saver_mode = 4 [ (android.privacy).dest = DEST_AUTOMATIC];
+ // If 1, battery saver (low_power_mode) will be re-activated after the device is
+ // unplugged from a charger or rebooted.
+ optional SettingProto sticky_enabled = 5;
+ // Whether sticky battery saver should be deactivated once the battery level has reached the
+ // threshold specified by sticky_disable_level.
+ optional SettingProto sticky_auto_disable_enabled = 6;
+ // When a device is unplugged from a changer (or is rebooted), do not re-activate battery
+ // saver even if {@link #LOW_POWER_MODE_STICKY} is 1, if the battery level is equal to or
+ // above this threshold.
+ optional SettingProto sticky_auto_disable_level = 7;
}
optional LowPowerMode low_power_mode = 70;
@@ -731,8 +743,7 @@
// Defines global runtime overrides to window policy.
optional SettingProto policy_control = 92;
optional SettingProto power_manager_constants = 93;
- // If true, out-of-the-box execution for priv apps is enabled.
- optional SettingProto priv_app_oob_enabled = 94 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ reserved 94; // Used to be priv_app_oob_enabled
message PrepaidSetup {
option (android.msg_privacy).dest = DEST_EXPLICIT;
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 4bfd4d2..aaf6c63 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -528,8 +528,11 @@
optional SettingProto skip_gesture_enabled = 74 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto silence_gesture_enabled = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto theme_customization_overlay_packages = 76 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+ optional SettingProto aware_enabled = 77 [ (android.privacy).dest = DEST_AUTOMATIC ];
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 76;
+ // Next tag = 78;
}
diff --git a/core/proto/android/server/connectivity/data_stall_event.proto b/core/proto/android/server/connectivity/data_stall_event.proto
index b70bb67..21717d8 100644
--- a/core/proto/android/server/connectivity/data_stall_event.proto
+++ b/core/proto/android/server/connectivity/data_stall_event.proto
@@ -41,7 +41,7 @@
RADIO_TECHNOLOGY_UMTS = 3;
RADIO_TECHNOLOGY_IS95A = 4;
RADIO_TECHNOLOGY_IS95B = 5;
- RADIO_TECHNOLOGY_1xRTT = 6;
+ RADIO_TECHNOLOGY_1XRTT = 6;
RADIO_TECHNOLOGY_EVDO_0 = 7;
RADIO_TECHNOLOGY_EVDO_A = 8;
RADIO_TECHNOLOGY_HSDPA = 9;
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 7f3ea7a..e68f9db 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -221,6 +221,8 @@
optional bool use_heartbeats = 23;
message TimeController {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Whether or not TimeController should skip setting wakeup alarms for jobs that aren't
// ready now.
optional bool skip_not_ready_jobs = 1;
@@ -228,6 +230,8 @@
optional TimeController time_controller = 25;
message QuotaController {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
// How much time each app will have to run jobs within their standby bucket window.
optional int64 allowed_time_per_period_ms = 1;
// How much time the package should have before transitioning from out-of-quota to in-quota.
@@ -251,6 +255,21 @@
optional int64 rare_window_size_ms = 6;
// The maximum amount of time an app can have its jobs running within a 24 hour window.
optional int64 max_execution_time_ms = 7;
+ // The maximum number of jobs an app can run within this particular standby bucket's
+ // window size.
+ optional int32 max_job_count_active = 8;
+ // The maximum number of jobs an app can run within this particular standby bucket's
+ // window size.
+ optional int32 max_job_count_working = 9;
+ // The maximum number of jobs an app can run within this particular standby bucket's
+ // window size.
+ optional int32 max_job_count_frequent = 10;
+ // The maximum number of jobs an app can run within this particular standby bucket's
+ // window size.
+ optional int32 max_job_count_rare = 11;
+ // The maximum number of jobs that should be allowed to run in the past
+ // {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}.
+ optional int32 max_job_count_per_allowed_time = 12;
}
optional QuotaController quota_controller = 24;
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index cee556a..af0a942 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -350,4 +350,12 @@
// The value of Global.LOW_POWER_MODE_TRIGGER_LEVEL. This is a cached value, so it could
// be slightly different from what's in GlobalSettingsProto.LowPowerMode.
optional int32 setting_battery_saver_trigger_threshold = 11;
+
+ // The value of Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED. This is a cached value, so
+ // it could be slightly different from what's in GlobalSettingsProto.LowPowerMode.
+ optional bool setting_battery_saver_sticky_auto_disable_enabled = 12;
+
+ // The value of Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL. This is a cached value, so it
+ // could be slightly different from what's in GlobalSettingsProto.LowPowerMode.
+ optional int32 setting_battery_saver_sticky_auto_disable_threshold = 13;
}
diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto
index 528c1a4..050ec7a 100644
--- a/core/proto/android/server/usagestatsservice.proto
+++ b/core/proto/android/server/usagestatsservice.proto
@@ -88,6 +88,11 @@
// If class field is an Activity, instance_id is a unique id of the
// Activity object.
optional int32 instance_id = 14;
+ // task_root_package_index contains the index + 1 of the task root package name in the string
+ // pool
+ optional int32 task_root_package_index = 15;
+ // task_root_class_index contains the index + 1 of the task root class name in the string pool
+ optional int32 task_root_class_index = 16;
}
// The following fields contain supplemental data used to build IntervalStats, such as a string
diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto
index f7dcee2..367c540 100644
--- a/core/proto/android/service/usb.proto
+++ b/core/proto/android/service/usb.proto
@@ -228,6 +228,15 @@
repeated Mode supported_modes = 2;
}
+/* Same as android.hardware.usb.V1_2.Constants.ContaminantPresenceStatus */
+enum ContaminantPresenceStatus {
+ CONTAMINANT_STATUS_UNKNOWN = 0;
+ CONTAMINANT_STATUS_NOT_SUPPORTED = 1;
+ CONTAMINANT_STATUS_DISABLED = 2;
+ CONTAMINANT_STATUS_NOT_DETECTED = 3;
+ CONTAMINANT_STATUS_DETECTED = 4;
+}
+
message UsbPortStatusProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -250,6 +259,7 @@
optional PowerRole power_role = 3;
optional DataRole data_role = 4;
repeated UsbPortStatusRoleCombinationProto role_combinations = 5;
+ optional ContaminantPresenceStatus contaminant_presence_status = 6;
}
message UsbPortStatusRoleCombinationProto {
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 82460ec..a8e64c6 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -92,8 +92,7 @@
SET_UNINSTALL_BLOCKED = 67;
SET_PACKAGES_SUSPENDED = 68;
ON_LOCK_TASK_MODE_ENTERING = 69;
- ADD_CROSS_PROFILE_CALENDAR_PACKAGE = 70;
- REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE = 71;
+ SET_CROSS_PROFILE_CALENDAR_PACKAGES = 70;
GET_USER_PASSWORD_COMPLEXITY_LEVEL = 72;
INSTALL_SYSTEM_UPDATE = 73;
INSTALL_SYSTEM_UPDATE_ERROR = 74;
diff --git a/core/proto/android/view/remote_animation_target.proto b/core/proto/android/view/remote_animation_target.proto
index fb4d5bd..808c514 100644
--- a/core/proto/android/view/remote_animation_target.proto
+++ b/core/proto/android/view/remote_animation_target.proto
@@ -42,4 +42,6 @@
optional .android.graphics.PointProto position = 8;
optional .android.graphics.RectProto source_container_bounds = 9;
optional .android.app.WindowConfigurationProto window_configuration = 10;
+ optional .android.view.SurfaceControlProto start_leash = 11;
+ optional .android.graphics.RectProto start_bounds = 12;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ea0c8e2..f92df6a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -487,6 +487,7 @@
<protected-broadcast android:name="android.security.action.TRUST_STORE_CHANGED" />
<protected-broadcast android:name="android.security.action.KEYCHAIN_CHANGED" />
<protected-broadcast android:name="android.security.action.KEY_ACCESS_CHANGED" />
+ <protected-broadcast android:name="android.telecom.action.NUISANCE_CALL_STATUS_CHANGED" />
<protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_REGISTERED" />
<protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_UNREGISTERED" />
<protected-broadcast android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION" />
@@ -1653,6 +1654,11 @@
<permission android:name="android.permission.WIFI_SET_DEVICE_MOBILITY_STATE"
android:protectionLevel="signature|privileged" />
+ <!-- #SystemApi @hide Allows privileged system APK to update Wifi usability stats and score.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE"
+ android:protectionLevel="signature|privileged" />
+
<!-- ======================================= -->
<!-- Permissions for short range, peripheral networks -->
<!-- ======================================= -->
@@ -1748,6 +1754,10 @@
<permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED"
android:protectionLevel="signature" />
+ <!-- @hide Allows the device to be reset, clearing all data and enables Test Harness Mode. -->
+ <permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE"
+ android:protectionLevel="signature" />
+
<!-- ================================== -->
<!-- Permissions for accessing accounts -->
<!-- ================================== -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 8ef264a..de6468d 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -950,6 +950,17 @@
<p>The default value of this attribute is <code>false</code>. -->
<attr name="allowEmbedded" format="boolean" />
+ <!-- @hide @SystemApi Specifies whether this {@link android.app.Activity} should be shown on
+ top of the lock screen whenever the lockscreen is up and this activity has another
+ activity behind it with the {@link android.R.attr#showWhenLocked} attribute set. That
+ is, this activity is only visible on the lock screen if there is another activity with
+ the {@link android.R.attr#showWhenLocked} attribute visible at the same time on the
+ lock screen. A use case for this is permission dialogs, that should only be visible on
+ the lock screen if their requesting activity is also visible.
+
+ <p>The default value of this attribute is <code>false</code>. -->
+ <attr name="inheritShowWhenLocked" format="boolean" />
+
<!-- Descriptive text for the associated data. -->
<attr name="description" format="reference" />
@@ -2415,18 +2426,6 @@
<attr name="showForAllUsers" />
<attr name="showWhenLocked" />
- <!-- @hide @SystemApi Specifies whether this {@link android.app.Activity} should be shown on
- top of the lock screen whenever the lockscreen is up and this activity has another
- activity behind it with the {@link android.R.attr#showWhenLocked} attribute set. That
- is, this activity is only visible on the lock screen if there is another activity with
- the {@link android.R.attr#showWhenLocked} attribute visible at the same time on the
- lock screen. A use case for this is permission dialogs, that should only be visible on
- the lock screen if their requesting activity is also visible.
-
- The default value of this attribute is <code>false</code>. -->
- <attr name="inheritShowWhenLocked" format="boolean" />
-
-
<attr name="inheritShowWhenLocked" />
<attr name="turnScreenOn" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5995640..bbc55a3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -126,6 +126,9 @@
be sent during a change to the audio output device. -->
<bool name="config_sendAudioBecomingNoisy">true</bool>
+ <!-- Whether Hearing Aid profile is supported -->
+ <bool name="config_hearing_aid_profile_supported">true</bool>
+
<!-- Flag to disable all transition animations -->
<bool name="config_disableTransitionAnimation">false</bool>
@@ -381,7 +384,7 @@
<!-- Configuration of Ethernet interfaces in the following format:
- <interface name|mac address>;[Network Capabilities];[IP config]
+ <interface name|mac address>;[Network Capabilities];[IP config];[Override Transport]
Where
[Network Capabilities] Optional. A comma seprated list of network capabilities.
Values must be from NetworkCapabilities#NET_CAPABILITIES_* constants.
@@ -389,11 +392,16 @@
use the following format to specify static IP configuration:
ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
domains=<comma-sep-domains>
+ [Override Transport] Optional. An override network transport type to allow
+ the propagation of an interface type on the other end of a local Ethernet
+ interface. Value must be from NetworkCapabilities#TRANSPORT_* constants. If
+ left out, this will default to TRANSPORT_ETHERNET.
-->
<string-array translatable="false" name="config_ethernet_interfaces">
<!--
<item>eth1;12,13,14,15;ip=192.168.0.10/24 gateway=192.168.0.1 dns=4.4.4.4,8.8.8.8</item>
<item>eth2;;ip=192.168.0.11/24</item>
+ <item>eth3;12,13,14,15;ip=192.168.0.12/24;1</item>
-->
</string-array>
@@ -525,6 +533,9 @@
<!-- Boolean indicating whether the wifi chipset has dual frequency band support -->
<bool translatable="false" name="config_wifi_dual_band_support">false</bool>
+ <!-- Maximum number of concurrent WiFi interfaces in AP mode -->
+ <integer translatable="false" name="config_wifi_max_ap_interfaces">1</integer>
+
<!-- Boolean indicating whether the wifi chipset requires the softap band be -->
<!-- converted from 5GHz to ANY due to hardware restrictions -->
<bool translatable="false" name="config_wifi_convert_apband_5ghz_to_any">false</bool>
@@ -705,6 +716,9 @@
Software implementation will be used if config_hardware_auto_brightness_available is not set -->
<bool name="config_automatic_brightness_available">false</bool>
+ <!-- Flag indicating whether we should enable the adaptive sleep.-->
+ <bool name="config_adaptive_sleep_available">false</bool>
+
<!-- Flag indicating whether we should enable smart battery. -->
<bool name="config_smart_battery_available">false</bool>
@@ -931,6 +945,10 @@
in hardware. -->
<bool name="config_setColorTransformAccelerated">false</bool>
+ <!-- Boolean indicating whether the HWC setColorTransform function can be performed efficiently
+ in hardware for individual layers. -->
+ <bool name="config_setColorTransformAcceleratedPerLayer">false</bool>
+
<!-- Control whether Night display is available. This should only be enabled on devices
that have a HWC implementation that can apply the matrix passed to setColorTransform
without impacting power, performance, and app compatibility (e.g. protected content). -->
@@ -1743,6 +1761,19 @@
config_enableGeofenceOverlay is false. -->
<string name="config_geofenceProviderPackageName" translatable="false">@null</string>
+ <!-- Whether to enable Hardware Activity-Recognition overlay which allows Hardware
+ Activity-Recognition to be replaced by an app at run-time. When disabled, only the
+ config_activityRecognitionHardwarePackageName package will be searched for
+ its implementation, otherwise packages whose signature matches the
+ signatures of config_locationProviderPackageNames will be searched, and
+ the service with the highest version number will be picked. Anyone who
+ wants to disable the overlay mechanism can set it to false.
+ -->
+ <bool name="config_enableActivityRecognitionHardwareOverlay" translatable="false">true</bool>
+ <!-- Package name providing Hardware Activity-Recognition API support. Used only when
+ config_enableActivityRecognitionHardwareOverlay is false. -->
+ <string name="config_activityRecognitionHardwarePackageName" translatable="false">@null</string>
+
<!-- Package name(s) containing location provider support.
These packages can contain services implementing location providers,
such as the Geocode Provider, Network Location Provider, and
@@ -3728,4 +3759,10 @@
<!-- Enable Zram writeback feature to allow unused pages in zram be written to flash. -->
<bool name="config_zramWriteback">false</bool>
+
+ <!-- Whether cbrs is supported on the device or not -->
+ <bool translatable="false" name="config_cbrs_supported">false</bool>
+
+ <!-- Whether or not aware is enabled by default -->
+ <bool name="config_awareSettingAvailable">false</bool>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 777886a..4235341 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2944,6 +2944,8 @@
</public-group>
<public-group type="style" first-id="0x010302e2">
+ <!-- @hide @SystemApi -->
+ <public name="Theme.DeviceDefault.DocumentsUI" />
</public-group>
<public-group type="id" first-id="0x01020046">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0878562..a761baf 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3353,9 +3353,9 @@
<string name="wifi_available_action_all_networks">All networks</string>
<!-- Notification title for a connection to a app suggested wireless network.-->
- <string name="wifi_suggestion_title">Connected to Wi\u2011Fi network proposed by <xliff:g id="name" example="App123">%s</xliff:g></string>
+ <string name="wifi_suggestion_title">A Wi\u2011Fi network proposed by <xliff:g id="name" example="App123">%s</xliff:g> is available</string>
<!-- Notification content for a connection to a app suggested wireless network.-->
- <string name="wifi_suggestion_content">Do you want to let <xliff:g id="name" example="App123">%s</xliff:g> propose networks for you?</string>
+ <string name="wifi_suggestion_content">Do you want to connect to networks proposed by <xliff:g id="name" example="App123">%s</xliff:g>?</string>
<!-- Notification action for allowing app specified in the notification body.-->
<string name="wifi_suggestion_action_allow_app">Yes</string>
<!-- Notification action for disallowing app specified in the notification body.-->
@@ -3387,6 +3387,9 @@
<!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. -->
<string name="wifi_no_internet_detailed">Tap for options</string>
+ <!-- A notification is shown after the user logs in to a captive portal network, to indicate that the network should now have internet connectivity. This is the message of notification. [CHAR LIMIT=50] -->
+ <string name="captive_portal_logged_in_detailed">Connected</string>
+
<!-- A notification is shown when the user's softap config has been changed due to underlying
hardware restrictions. This is the notifications's title.
[CHAR_LIMIT=NONE] -->
@@ -3573,6 +3576,15 @@
<string name="adb_active_notification_message">Tap to turn off USB debugging</string>
<string name="adb_active_notification_message" product="tv">Select to disable USB debugging.</string>
+ <!-- Title of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
+ <string name="usb_contaminant_detected_title">Liquid or debris in USB port</string>
+ <!-- Message of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
+ <string name="usb_contaminant_detected_message">USB port is automatically disabled. Tap to learn more.</string>
+ <!-- Title of notification shown when contaminant is no longer detected on the USB port. [CHAR LIMIT=NONE] -->
+ <string name="usb_contaminant_not_detected_title">Safe to use USB port</string>
+ <!-- Message of notification shown when contaminant is no longer detected on the USB port. [CHAR LIMIT=NONE] -->
+ <string name="usb_contaminant_not_detected_message">Phone no longer detects liquid or debris.</string>
+
<!-- Title of notification shown to indicate that bug report is being collected. -->
<string name="taking_remote_bugreport_notification_title">Taking bug report\u2026</string>
<!-- Title of notification shown to ask for user consent for sharing a bugreport that was requested remotely by the IT administrator. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 53c33a3..cbb4cb2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -437,6 +437,7 @@
<java-symbol type="integer" name="config_bluetooth_operating_voltage_mv" />
<java-symbol type="bool" name="config_bluetooth_pan_enable_autoconnect" />
<java-symbol type="bool" name="config_bluetooth_reload_supported_profiles_when_enabled" />
+ <java-symbol type="bool" name="config_hearing_aid_profile_supported" />
<java-symbol type="integer" name="config_cursorWindowSize" />
<java-symbol type="integer" name="config_drawLockTimeoutMillis" />
<java-symbol type="integer" name="config_doublePressOnPowerBehavior" />
@@ -694,6 +695,7 @@
<java-symbol type="string" name="capability_title_canControlMagnification" />
<java-symbol type="string" name="capability_desc_canPerformGestures" />
<java-symbol type="string" name="capability_title_canPerformGestures" />
+ <java-symbol type="string" name="captive_portal_logged_in_detailed" />
<java-symbol type="string" name="cfTemplateForwarded" />
<java-symbol type="string" name="cfTemplateForwardedTime" />
<java-symbol type="string" name="cfTemplateNotForwarded" />
@@ -1845,6 +1847,7 @@
<java-symbol type="array" name="config_defaultNotificationVibePattern" />
<java-symbol type="array" name="config_notificationFallbackVibePattern" />
<java-symbol type="bool" name="config_useAttentionLight" />
+ <java-symbol type="bool" name="config_adaptive_sleep_available" />
<java-symbol type="bool" name="config_animateScreenLights" />
<java-symbol type="bool" name="config_automatic_brightness_available" />
<java-symbol type="bool" name="config_smart_battery_available" />
@@ -1853,6 +1856,7 @@
<java-symbol type="bool" name="config_enableNightMode" />
<java-symbol type="bool" name="config_tintNotificationActionButtons" />
<java-symbol type="bool" name="config_dozeAfterScreenOffByDefault" />
+ <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
<java-symbol type="bool" name="config_enableFusedLocationOverlay" />
<java-symbol type="bool" name="config_enableHardwareFlpOverlay" />
<java-symbol type="bool" name="config_enableGeocoderOverlay" />
@@ -1877,6 +1881,7 @@
<java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" />
<java-symbol type="bool" name="config_wifi_background_scan_support" />
<java-symbol type="bool" name="config_wifi_dual_band_support" />
+ <java-symbol type="integer" name="config_wifi_max_ap_interfaces" />
<java-symbol type="bool" name="config_wifi_convert_apband_5ghz_to_any" />
<java-symbol type="bool" name="config_wifi_local_only_hotspot_5ghz" />
<java-symbol type="bool" name="config_wifi_connected_mac_randomization_supported" />
@@ -2021,6 +2026,7 @@
<java-symbol type="string" name="car_mode_disable_notification_title" />
<java-symbol type="string" name="chooser_wallpaper" />
<java-symbol type="string" name="config_datause_iface" />
+ <java-symbol type="string" name="config_activityRecognitionHardwarePackageName" />
<java-symbol type="string" name="config_fusedLocationProviderPackageName" />
<java-symbol type="string" name="config_hardwareFlpPackageName" />
<java-symbol type="string" name="config_geocoderProviderPackageName" />
@@ -2100,6 +2106,10 @@
<java-symbol type="string" name="usb_supplying_notification_title" />
<java-symbol type="string" name="usb_unsupported_audio_accessory_title" />
<java-symbol type="string" name="usb_unsupported_audio_accessory_message" />
+ <java-symbol type="string" name="usb_contaminant_detected_title" />
+ <java-symbol type="string" name="usb_contaminant_detected_message" />
+ <java-symbol type="string" name="usb_contaminant_not_detected_title" />
+ <java-symbol type="string" name="usb_contaminant_not_detected_message" />
<java-symbol type="string" name="config_UsbDeviceConnectionHandling_component" />
<java-symbol type="string" name="vpn_text" />
<java-symbol type="string" name="vpn_text_long" />
@@ -3031,6 +3041,7 @@
<java-symbol type="drawable" name="ic_doc_generic" />
<java-symbol type="bool" name="config_setColorTransformAccelerated" />
+ <java-symbol type="bool" name="config_setColorTransformAcceleratedPerLayer" />
<java-symbol type="bool" name="config_displayWhiteBalanceAvailable" />
<java-symbol type="bool" name="config_nightDisplayAvailable" />
<java-symbol type="bool" name="config_allowDisablingAssistDisclosure" />
@@ -3540,4 +3551,9 @@
<java-symbol type="bool" name="config_silenceSensorAvailable" />
<java-symbol type="bool" name="config_zramWriteback" />
+
+ <!-- For CBRS -->
+ <java-symbol type="bool" name="config_cbrs_supported" />
+
+ <java-symbol type="bool" name="config_awareSettingAvailable" />
</resources>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 75a727b..1603508 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1711,4 +1711,6 @@
<item name="notificationHeaderTextAppearance">@style/TextAppearance.DeviceDefault.Notification.Info</item>
</style>
+ <!-- @hide DeviceDefault theme for the DocumentsUI app. -->
+ <style name="Theme.DeviceDefault.DocumentsUI" parent="Theme.DeviceDefault.DayNight" />
</resources>
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
index d2bd1e1..11eb158 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
@@ -18,13 +18,13 @@
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.after;
-import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.testng.Assert.assertThrows;
import android.Manifest;
@@ -119,10 +119,9 @@
}
private void resetCallback() {
- verify(mCallback, atLeast(0)).onMetadataChanged(any());
- verify(mCallback, atLeast(0)).onProgramInfoChanged(any());
- verify(mCallback, atLeast(0)).onProgramListChanged();
- verifyNoMoreInteractions(mCallback);
+ verify(mCallback, never()).onError(anyInt());
+ verify(mCallback, never()).onTuneFailed(anyInt(), any());
+ verify(mCallback, never()).onControlChanged(anyBoolean());
Mockito.reset(mCallback);
}
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index 8d42c74..5731daa 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -20,12 +20,23 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.app.admin.PasswordMetrics.complexityLevelToMinQuality;
+import static android.app.admin.PasswordMetrics.getActualRequiredQuality;
+import static android.app.admin.PasswordMetrics.getMinimumMetrics;
+import static android.app.admin.PasswordMetrics.getTargetQualityMetrics;
+import static android.app.admin.PasswordMetrics.sanitizeComplexityLevel;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
-import android.app.admin.PasswordMetrics.PasswordComplexityBucket;
import android.os.Parcel;
import androidx.test.filters.SmallTest;
@@ -109,7 +120,7 @@
assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
PasswordMetrics.computeForPassword("1").quality);
// contains a long sequence so isn't complex
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
+ assertEquals(PASSWORD_QUALITY_NUMERIC,
PasswordMetrics.computeForPassword("1234").quality);
assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
PasswordMetrics.computeForPassword("").quality);
@@ -145,7 +156,7 @@
new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 5));
assertNotEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 4));
+ new PasswordMetrics(PASSWORD_QUALITY_COMPLEX, 4));
metrics0 = PasswordMetrics.computeForPassword("1234abcd,./");
metrics1 = PasswordMetrics.computeForPassword("1234abcd,./");
@@ -176,9 +187,9 @@
@Test
public void testConstructQuality() {
PasswordMetrics expected = new PasswordMetrics();
- expected.quality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+ expected.quality = PASSWORD_QUALITY_COMPLEX;
- PasswordMetrics actual = new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
+ PasswordMetrics actual = new PasswordMetrics(PASSWORD_QUALITY_COMPLEX);
assertEquals(expected, actual);
}
@@ -256,42 +267,178 @@
}
@Test
- public void testComplexityLevelToBucket_none() {
- PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
- PASSWORD_COMPLEXITY_NONE).getMetrics();
+ public void testSanitizeComplexityLevel_none() {
+ assertEquals(PASSWORD_COMPLEXITY_NONE, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_NONE));
- for (PasswordMetrics metrics : bucket) {
- assertEquals(PASSWORD_COMPLEXITY_NONE, metrics.determineComplexity());
- }
}
@Test
- public void testComplexityLevelToBucket_low() {
- PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
- PASSWORD_COMPLEXITY_LOW).getMetrics();
-
- for (PasswordMetrics metrics : bucket) {
- assertEquals(PASSWORD_COMPLEXITY_LOW, metrics.determineComplexity());
- }
+ public void testSanitizeComplexityLevel_low() {
+ assertEquals(PASSWORD_COMPLEXITY_LOW, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_LOW));
}
@Test
- public void testComplexityLevelToBucket_medium() {
- PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
- PASSWORD_COMPLEXITY_MEDIUM).getMetrics();
-
- for (PasswordMetrics metrics : bucket) {
- assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metrics.determineComplexity());
- }
+ public void testSanitizeComplexityLevel_medium() {
+ assertEquals(
+ PASSWORD_COMPLEXITY_MEDIUM, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_MEDIUM));
}
@Test
- public void testComplexityLevelToBucket_high() {
- PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
- PASSWORD_COMPLEXITY_HIGH).getMetrics();
+ public void testSanitizeComplexityLevel_high() {
+ assertEquals(PASSWORD_COMPLEXITY_HIGH, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_HIGH));
+ }
- for (PasswordMetrics metrics : bucket) {
- assertEquals(PASSWORD_COMPLEXITY_HIGH, metrics.determineComplexity());
- }
+ @Test
+ public void testSanitizeComplexityLevel_invalid() {
+ assertEquals(PASSWORD_COMPLEXITY_NONE, sanitizeComplexityLevel(-1));
+ }
+
+ @Test
+ public void testComplexityLevelToMinQuality_none() {
+ assertEquals(PASSWORD_QUALITY_UNSPECIFIED,
+ complexityLevelToMinQuality(PASSWORD_COMPLEXITY_NONE));
+ }
+
+ @Test
+ public void testComplexityLevelToMinQuality_low() {
+ assertEquals(PASSWORD_QUALITY_SOMETHING,
+ complexityLevelToMinQuality(PASSWORD_COMPLEXITY_LOW));
+ }
+
+ @Test
+ public void testComplexityLevelToMinQuality_medium() {
+ assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX,
+ complexityLevelToMinQuality(PASSWORD_COMPLEXITY_MEDIUM));
+ }
+
+ @Test
+ public void testComplexityLevelToMinQuality_high() {
+ assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX,
+ complexityLevelToMinQuality(PASSWORD_COMPLEXITY_HIGH));
+ }
+
+ @Test
+ public void testComplexityLevelToMinQuality_invalid() {
+ assertEquals(PASSWORD_QUALITY_UNSPECIFIED, complexityLevelToMinQuality(-1));
+ }
+
+ @Test
+ public void testGetTargetQualityMetrics_noneComplexityReturnsDefaultMetrics() {
+ PasswordMetrics metrics =
+ getTargetQualityMetrics(PASSWORD_COMPLEXITY_NONE, PASSWORD_QUALITY_ALPHANUMERIC);
+
+ assertTrue(metrics.isDefault());
+ }
+
+ @Test
+ public void testGetTargetQualityMetrics_qualityNotAllowedReturnsMinQualityMetrics() {
+ PasswordMetrics metrics =
+ getTargetQualityMetrics(PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_NUMERIC);
+
+ assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
+ assertEquals(/* expected= */ 4, metrics.length);
+ }
+
+ @Test
+ public void testGetTargetQualityMetrics_highComplexityNumericComplex() {
+ PasswordMetrics metrics = getTargetQualityMetrics(
+ PASSWORD_COMPLEXITY_HIGH, PASSWORD_QUALITY_NUMERIC_COMPLEX);
+
+ assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
+ assertEquals(/* expected= */ 8, metrics.length);
+ }
+
+ @Test
+ public void testGetTargetQualityMetrics_mediumComplexityAlphabetic() {
+ PasswordMetrics metrics = getTargetQualityMetrics(
+ PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHABETIC);
+
+ assertEquals(PASSWORD_QUALITY_ALPHABETIC, metrics.quality);
+ assertEquals(/* expected= */ 4, metrics.length);
+ }
+
+ @Test
+ public void testGetTargetQualityMetrics_lowComplexityAlphanumeric() {
+ PasswordMetrics metrics = getTargetQualityMetrics(
+ PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHANUMERIC);
+
+ assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality);
+ assertEquals(/* expected= */ 4, metrics.length);
+ }
+
+ @Test
+ public void testGetActualRequiredQuality_nonComplex() {
+ int actual = getActualRequiredQuality(
+ PASSWORD_QUALITY_NUMERIC_COMPLEX,
+ /* requiresNumeric= */ false,
+ /* requiresLettersOrSymbols= */ false);
+
+ assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, actual);
+ }
+
+ @Test
+ public void testGetActualRequiredQuality_complexRequiresNone() {
+ int actual = getActualRequiredQuality(
+ PASSWORD_QUALITY_COMPLEX,
+ /* requiresNumeric= */ false,
+ /* requiresLettersOrSymbols= */ false);
+
+ assertEquals(PASSWORD_QUALITY_UNSPECIFIED, actual);
+ }
+
+ @Test
+ public void testGetActualRequiredQuality_complexRequiresNumeric() {
+ int actual = getActualRequiredQuality(
+ PASSWORD_QUALITY_COMPLEX,
+ /* requiresNumeric= */ true,
+ /* requiresLettersOrSymbols= */ false);
+
+ assertEquals(PASSWORD_QUALITY_NUMERIC, actual);
+ }
+
+ @Test
+ public void testGetActualRequiredQuality_complexRequiresLetters() {
+ int actual = getActualRequiredQuality(
+ PASSWORD_QUALITY_COMPLEX,
+ /* requiresNumeric= */ false,
+ /* requiresLettersOrSymbols= */ true);
+
+ assertEquals(PASSWORD_QUALITY_ALPHABETIC, actual);
+ }
+
+ @Test
+ public void testGetActualRequiredQuality_complexRequiresNumericAndLetters() {
+ int actual = getActualRequiredQuality(
+ PASSWORD_QUALITY_COMPLEX,
+ /* requiresNumeric= */ true,
+ /* requiresLettersOrSymbols= */ true);
+
+ assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, actual);
+ }
+
+ @Test
+ public void testGetMinimumMetrics_userInputStricter() {
+ PasswordMetrics metrics = getMinimumMetrics(
+ PASSWORD_COMPLEXITY_HIGH,
+ PASSWORD_QUALITY_ALPHANUMERIC,
+ PASSWORD_QUALITY_NUMERIC,
+ /* requiresNumeric= */ false,
+ /* requiresLettersOrSymbols= */ false);
+
+ assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality);
+ assertEquals(/* expected= */ 6, metrics.length);
+ }
+
+ @Test
+ public void testGetMinimumMetrics_actualRequiredQualityStricter() {
+ PasswordMetrics metrics = getMinimumMetrics(
+ PASSWORD_COMPLEXITY_HIGH,
+ PASSWORD_QUALITY_UNSPECIFIED,
+ PASSWORD_QUALITY_NUMERIC,
+ /* requiresNumeric= */ false,
+ /* requiresLettersOrSymbols= */ false);
+
+ assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
+ assertEquals(/* expected= */ 8, metrics.length);
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 8604b0c..bdf3aa2 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -38,7 +38,6 @@
import android.content.pm.ServiceInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
-import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
@@ -501,7 +500,7 @@
}
@Override
- public void setHttpProxy(String s, String s1, String s2, Uri uri) throws RemoteException {
+ public void updateHttpProxy() throws RemoteException {
}
@Override
diff --git a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
index 3d7aab0..ad9814b 100644
--- a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
@@ -48,21 +48,11 @@
}
/**
- * Detect when the org.apache.http.legacy is not on the bootclasspath.
- *
- * <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and
- * succeed otherwise. This allows a developer to ensure that the tests are being
- */
- @Test
- public void detectWhenOAHLisOnBCP() {
- Assume.assumeTrue(PackageBackwardCompatibility.bootClassPathContainsOAHL());
- }
-
- /**
* Detect when the android.test.base is not on the bootclasspath.
*
* <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and
- * succeed otherwise. This allows a developer to ensure that the tests are being
+ * succeed otherwise. This allows a developer to ensure that the tests are being run in the
+ * correct environment.
*/
@Test
public void detectWhenATBisOnBCP() {
@@ -85,9 +75,7 @@
if (!PackageBackwardCompatibility.bootClassPathContainsATB()) {
expected.add(ANDROID_TEST_BASE);
}
- if (!PackageBackwardCompatibility.bootClassPathContainsOAHL()) {
- expected.add(ORG_APACHE_HTTP_LEGACY);
- }
+ expected.add(ORG_APACHE_HTTP_LEGACY);
PackageBuilder after = builder()
.targetSdkVersion(Build.VERSION_CODES.O)
@@ -98,30 +86,6 @@
/**
* Ensures that the {@link PackageBackwardCompatibility} uses
- * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest}
- * when necessary.
- *
- * <p>More comprehensive tests for that class can be found in
- * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest}.
- */
- @Test
- public void org_apache_http_legacy_in_usesLibraries() {
- Assume.assumeTrue("Test requires that "
- + ORG_APACHE_HTTP_LEGACY + " is on the bootclasspath",
- PackageBackwardCompatibility.bootClassPathContainsOAHL());
-
- PackageBuilder before = builder()
- .requiredLibraries(ORG_APACHE_HTTP_LEGACY);
-
- // org.apache.http.legacy should be removed from the libraries because it is provided
- // on the bootclasspath and providing both increases start up cost unnecessarily.
- PackageBuilder after = builder();
-
- checkBackwardsCompatibility(before, after);
- }
-
- /**
- * Ensures that the {@link PackageBackwardCompatibility} uses
* {@link RemoveUnnecessaryAndroidTestBaseLibrary}
* when necessary.
*
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
index c5454a6..300394d 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -527,12 +527,16 @@
R.raw.com_android_tzdata);
PackageInfo pi = PackageParser.generatePackageInfoFromApex(apexFile, false);
assertEquals("com.google.android.tzdata", pi.packageName);
+ assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
assertEquals(1, pi.getLongVersionCode());
+ assertEquals(1, pi.applicationInfo.longVersionCode);
assertNull(pi.signingInfo);
pi = PackageParser.generatePackageInfoFromApex(apexFile, true);
assertEquals("com.google.android.tzdata", pi.packageName);
+ assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
assertEquals(1, pi.getLongVersionCode());
+ assertEquals(1, pi.applicationInfo.longVersionCode);
assertNotNull(pi.signingInfo);
assertTrue(pi.signingInfo.getApkContentsSigners().length > 0);
}
diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
index 425ab89..e94d60c 100644
--- a/core/tests/coretests/src/android/os/WorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -341,12 +341,37 @@
}
public void testGetAttributionId() {
- WorkSource ws1 = new WorkSource();
- WorkChain wc = ws1.createWorkChain();
- wc.addNode(100, "tag");
- assertEquals(100, wc.getAttributionUid());
- wc.addNode(200, "tag2");
- assertEquals(100, wc.getAttributionUid());
+ WorkSource ws = new WorkSource();
+ WorkChain wc1 = ws.createWorkChain();
+ wc1.addNode(100, "tag");
+ assertEquals(100, wc1.getAttributionUid());
+ assertEquals(100, ws.getAttributionUid());
+ wc1.addNode(200, "tag2");
+ assertEquals(100, wc1.getAttributionUid());
+ assertEquals(100, ws.getAttributionUid());
+ WorkChain wc2 = ws.createWorkChain();
+ wc2.addNode(300, "tag3");
+ assertEquals(300, wc2.getAttributionUid());
+ assertEquals(100, ws.getAttributionUid());
+ }
+
+ public void testGetAttributionIdWithoutWorkChain() {
+ WorkSource ws1 = new WorkSource(100);
+ ws1.add(200);
+ WorkSource ws2 = new WorkSource();
+ ws2.add(100);
+ ws2.add(200);
+ assertEquals(100, ws1.getAttributionUid());
+ assertEquals(100, ws2.getAttributionUid());
+ }
+
+ public void testGetAttributionWhenEmpty() {
+ WorkSource ws = new WorkSource();
+ assertEquals(-1, ws.getAttributionUid());
+ WorkChain wc = ws.createWorkChain();
+ assertEquals(-1, ws.getAttributionUid());
+ assertEquals(-1, wc.getAttributionUid());
+ assertNull(wc.getAttributionTag());
}
public void testGetAttributionTag() {
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 87ad3d1..a010cb6 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -100,6 +100,7 @@
Settings.Global.ACTIVITY_MANAGER_CONSTANTS,
Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED,
Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED,
+ Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
Settings.Global.ADB_ENABLED,
Settings.Global.ADD_USERS_WHEN_LOCKED,
Settings.Global.AIRPLANE_MODE_ON,
@@ -120,6 +121,7 @@
Settings.Global.APP_IDLE_CONSTANTS,
Settings.Global.APP_OPS_CONSTANTS,
Settings.Global.APP_STANDBY_ENABLED,
+ Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE,
Settings.Global.ASSISTED_GPS_ENABLED,
Settings.Global.AUDIO_SAFE_VOLUME_STATE,
Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
@@ -296,6 +298,7 @@
Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
+ Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS,
Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
Settings.Global.LOCATION_GLOBAL_KILL_SWITCH,
@@ -386,8 +389,6 @@
Settings.Global.POLICY_CONTROL,
Settings.Global.POWER_MANAGER_CONSTANTS,
Settings.Global.PREFERRED_NETWORK_MODE,
- Settings.Global.PRIV_APP_OOB_ENABLED,
- Settings.Global.PRIV_APP_OOB_LIST,
Settings.Global.PRIVATE_DNS_DEFAULT_MODE,
Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED,
Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED,
@@ -485,6 +486,7 @@
Settings.Global.GUP_DEV_OPT_IN_APPS,
Settings.Global.GUP_DEV_OPT_OUT_APPS,
Settings.Global.GUP_BLACKLIST,
+ Settings.Global.GAME_DRIVER_WHITELIST,
Settings.Global.GPU_DEBUG_LAYER_APP,
Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
Settings.Global.INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT,
@@ -562,7 +564,9 @@
Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS,
Settings.Global.ENABLE_RADIO_BUG_DETECTION,
Settings.Global.RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD,
- Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD);
+ Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD,
+ Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT,
+ Settings.Global.MODEM_STACK_ENABLED_FOR_SLOT);
private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
newHashSet(
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
diff --git a/core/tests/coretests/src/android/util/LongArrayQueueTest.java b/core/tests/coretests/src/android/util/LongArrayQueueTest.java
new file mode 100644
index 0000000..77e8d60
--- /dev/null
+++ b/core/tests/coretests/src/android/util/LongArrayQueueTest.java
@@ -0,0 +1,238 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Internal tests for {@link LongArrayQueue}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LongArrayQueueTest {
+
+ private LongArrayQueue mQueueUnderTest;
+
+ @Before
+ public void setUp() {
+ mQueueUnderTest = new LongArrayQueue();
+ }
+
+ @Test
+ public void removeFirstOnEmptyQueue() {
+ try {
+ mQueueUnderTest.removeFirst();
+ fail("removeFirst() succeeded on an empty queue!");
+ } catch (NoSuchElementException e) {
+ }
+ mQueueUnderTest.addLast(5);
+ mQueueUnderTest.removeFirst();
+ try {
+ mQueueUnderTest.removeFirst();
+ fail("removeFirst() succeeded on an empty queue!");
+ } catch (NoSuchElementException e) {
+ }
+ }
+
+ @Test
+ public void addLastRemoveFirstFifo() {
+ mQueueUnderTest.addLast(1);
+ assertEquals(1, mQueueUnderTest.removeFirst());
+ int n = 890;
+ int removes = 0;
+ for (int i = 0; i < n; i++) {
+ mQueueUnderTest.addLast(i);
+ if ((i % 3) == 0) {
+ assertEquals(removes++, mQueueUnderTest.removeFirst());
+ }
+ }
+ while (removes < n) {
+ assertEquals(removes++, mQueueUnderTest.removeFirst());
+ }
+ }
+
+ @Test
+ public void peekFirstOnEmptyQueue() {
+ try {
+ mQueueUnderTest.peekFirst();
+ fail("peekFirst() succeeded on an empty queue!");
+ } catch (NoSuchElementException e) {
+ }
+ mQueueUnderTest.addLast(5);
+ mQueueUnderTest.removeFirst();
+ try {
+ mQueueUnderTest.peekFirst();
+ fail("peekFirst() succeeded on an empty queue!");
+ } catch (NoSuchElementException e) {
+ }
+ }
+
+ @Test
+ public void peekFirstChanges() {
+ mQueueUnderTest.addLast(1);
+ assertEquals(1, mQueueUnderTest.peekFirst());
+ mQueueUnderTest.addLast(2);
+ mQueueUnderTest.addLast(3);
+ mQueueUnderTest.addLast(4);
+ // addLast() has no effect on peekFirst().
+ assertEquals(1, mQueueUnderTest.peekFirst());
+ mQueueUnderTest.removeFirst();
+ mQueueUnderTest.removeFirst();
+ assertEquals(3, mQueueUnderTest.peekFirst());
+ }
+
+ @Test
+ public void peekLastOnEmptyQueue() {
+ try {
+ mQueueUnderTest.peekLast();
+ fail("peekLast() succeeded on an empty queue!");
+ } catch (NoSuchElementException e) {
+ }
+ mQueueUnderTest.addLast(5);
+ mQueueUnderTest.removeFirst();
+ try {
+ mQueueUnderTest.peekLast();
+ fail("peekLast() succeeded on an empty queue!");
+ } catch (NoSuchElementException e) {
+ }
+ }
+
+ @Test
+ public void peekLastChanges() {
+ mQueueUnderTest.addLast(1);
+ assertEquals(1, mQueueUnderTest.peekLast());
+ mQueueUnderTest.addLast(2);
+ mQueueUnderTest.addLast(3);
+ mQueueUnderTest.addLast(4);
+ assertEquals(4, mQueueUnderTest.peekLast());
+ mQueueUnderTest.removeFirst();
+ mQueueUnderTest.removeFirst();
+ // removeFirst() has no effect on peekLast().
+ assertEquals(4, mQueueUnderTest.peekLast());
+ }
+
+ @Test
+ public void peekFirstVsPeekLast() {
+ mQueueUnderTest.addLast(2);
+ assertEquals(mQueueUnderTest.peekFirst(), mQueueUnderTest.peekLast());
+ mQueueUnderTest.addLast(3);
+ assertNotEquals(mQueueUnderTest.peekFirst(), mQueueUnderTest.peekLast());
+ mQueueUnderTest.removeFirst();
+ assertEquals(mQueueUnderTest.peekFirst(), mQueueUnderTest.peekLast());
+ }
+
+ @Test
+ public void peekFirstVsRemoveFirst() {
+ int n = 25;
+ for (int i = 0; i < n; i++) {
+ mQueueUnderTest.addLast(i + 1);
+ }
+ for (int i = 0; i < n; i++) {
+ long peekVal = mQueueUnderTest.peekFirst();
+ assertEquals(peekVal, mQueueUnderTest.removeFirst());
+ }
+ }
+
+ @Test
+ public void sizeOfEmptyQueue() {
+ assertEquals(0, mQueueUnderTest.size());
+ mQueueUnderTest = new LongArrayQueue(1000);
+ // capacity doesn't affect size.
+ assertEquals(0, mQueueUnderTest.size());
+ }
+
+ @Test
+ public void sizeAfterOperations() {
+ final int added = 1200;
+ for (int i = 0; i < added; i++) {
+ mQueueUnderTest.addLast(i);
+ }
+ // each add increments the size by 1.
+ assertEquals(added, mQueueUnderTest.size());
+ mQueueUnderTest.peekLast();
+ mQueueUnderTest.peekFirst();
+ // peeks don't change the size.
+ assertEquals(added, mQueueUnderTest.size());
+ final int removed = 345;
+ for (int i = 0; i < removed; i++) {
+ mQueueUnderTest.removeFirst();
+ }
+ // each remove decrements the size by 1.
+ assertEquals(added - removed, mQueueUnderTest.size());
+ mQueueUnderTest.clear();
+ // clear reduces the size to 0.
+ assertEquals(0, mQueueUnderTest.size());
+ }
+
+ @Test
+ public void getInvalidPositions() {
+ try {
+ mQueueUnderTest.get(0);
+ fail("get(0) succeeded on an empty queue!");
+ } catch (IndexOutOfBoundsException e) {
+ }
+ int n = 520;
+ for (int i = 0; i < 2 * n; i++) {
+ mQueueUnderTest.addLast(i + 1);
+ }
+ for (int i = 0; i < n; i++) {
+ mQueueUnderTest.removeFirst();
+ }
+ try {
+ mQueueUnderTest.get(-3);
+ fail("get(-3) succeeded");
+ } catch (IndexOutOfBoundsException e) {
+ }
+ assertEquals(n, mQueueUnderTest.size());
+ try {
+ mQueueUnderTest.get(n);
+ fail("get(" + n + ") succeeded on a queue with " + n + " elements");
+ } catch (IndexOutOfBoundsException e) {
+ }
+ try {
+ mQueueUnderTest.get(n + 3);
+ fail("get(" + (n + 3) + ") succeeded on a queue with " + n + " elements");
+ } catch (IndexOutOfBoundsException e) {
+ }
+ }
+
+ @Test
+ public void getValidPositions() {
+ int added = 423;
+ int removed = 212;
+ for (int i = 0; i < added; i++) {
+ mQueueUnderTest.addLast(i);
+ }
+ for (int i = 0; i < removed; i++) {
+ mQueueUnderTest.removeFirst();
+ }
+ for (int i = 0; i < (added - removed); i++) {
+ assertEquals(removed + i, mQueueUnderTest.get(i));
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 7f00ad9..7cd3c44 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -101,6 +101,7 @@
@Test
public void testChangeInsets() {
mController.changeInsets(Insets.of(0, 30, 40, 0));
+ mController.applyChangeInsets(new InsetsState());
assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets());
ArgumentCaptor<SurfaceParams> captor = ArgumentCaptor.forClass(SurfaceParams.class);
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 9857b7a..2db2f5f 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -60,7 +60,7 @@
mState.getSource(TYPE_IME).setVisible(true);
SparseIntArray typeSideMap = new SparseIntArray();
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
- DisplayCutout.NO_CUTOUT, typeSideMap);
+ DisplayCutout.NO_CUTOUT, null, null, typeSideMap);
assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
assertEquals(INSET_SIDE_TOP, typeSideMap.get(TYPE_TOP_BAR));
@@ -76,7 +76,7 @@
mState.getSource(TYPE_IME).setFrame(new Rect(0, 100, 100, 300));
mState.getSource(TYPE_IME).setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
- DisplayCutout.NO_CUTOUT, null);
+ DisplayCutout.NO_CUTOUT, null, null, null);
assertEquals(100, insets.getStableInsetBottom());
assertEquals(Insets.of(0, 0, 0, 100), insets.getMaxInsets(Type.all()));
assertEquals(Insets.of(0, 0, 0, 200), insets.getSystemWindowInsets());
@@ -92,7 +92,7 @@
mState.getSource(TYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
mState.getSource(TYPE_NAVIGATION_BAR).setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
- DisplayCutout.NO_CUTOUT, null);
+ DisplayCutout.NO_CUTOUT, null, null, null);
assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.topBar()));
assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.sideBars()));
@@ -106,7 +106,7 @@
mState.getSource(TYPE_IME).setVisible(true);
mState.removeSource(TYPE_IME);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
- DisplayCutout.NO_CUTOUT, null);
+ DisplayCutout.NO_CUTOUT, null, null, null);
assertEquals(0, insets.getSystemWindowInsetBottom());
}
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index 15a96a1..d57fa8f 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -18,7 +18,7 @@
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.sideBars;
-
+import static android.view.WindowInsets.Type.topBar;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -54,6 +54,7 @@
assertTrue(new WindowInsets((Rect) null).isConsumed());
}
+ // TODO: Move this to CTS once API made public
@Test
public void typeMap() {
Builder b = new WindowInsets.Builder();
@@ -62,4 +63,14 @@
WindowInsets insets = b.build();
assertEquals(300, insets.getSystemWindowInsets().bottom);
}
+
+ // TODO: Move this to CTS once API made public
+ @Test
+ public void compatInsets() {
+ Builder b = new WindowInsets.Builder();
+ b.setSystemWindowInsets(Insets.of(0, 50, 30, 10));
+ WindowInsets insets = b.build();
+ assertEquals(Insets.of(0, 50, 0, 0), insets.getInsets(topBar()));
+ assertEquals(Insets.of(0, 0, 30, 10), insets.getInsets(sideBars()));
+ }
}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 0dd7685..683d16b 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -88,32 +88,32 @@
public void setOnKeyEventResult(boolean handled, int sequence) {}
- public float getMagnificationScale() {
+ public float getMagnificationScale(int displayId) {
return 0.0f;
}
- public float getMagnificationCenterX() {
+ public float getMagnificationCenterX(int displayId) {
return 0.0f;
}
- public float getMagnificationCenterY() {
+ public float getMagnificationCenterY(int displayId) {
return 0.0f;
}
- public Region getMagnificationRegion() {
+ public Region getMagnificationRegion(int displayId) {
return null;
}
- public boolean resetMagnification(boolean animate) {
+ public boolean resetMagnification(int displayId, boolean animate) {
return false;
}
- public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
- boolean animate) {
+ public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX,
+ float centerY, boolean animate) {
return false;
}
- public void setMagnificationCallbackEnabled(boolean enabled) {}
+ public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {}
public boolean setSoftKeyboardShowMode(int showMode) {
return false;
diff --git a/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
index 33bc593..2f17b32 100644
--- a/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
+++ b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
@@ -34,26 +34,57 @@
public void testNonVirtual() {
final AutofillId id = new AutofillId(42);
assertThat(id.getViewId()).isEqualTo(42);
- assertThat(id.isVirtual()).isFalse();
- assertThat(id.getVirtualChildId()).isEqualTo(View.NO_ID);
+ assertThat(id.isNonVirtual()).isTrue();
+ assertThat(id.isVirtualInt()).isFalse();
+ assertThat(id.isVirtualLong()).isFalse();
+ assertThat(id.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+ assertThat(id.getVirtualChildLongId()).isEqualTo(View.NO_ID);
final AutofillId clone = cloneThroughParcel(id);
assertThat(clone.getViewId()).isEqualTo(42);
- assertThat(clone.isVirtual()).isFalse();
- assertThat(clone.getVirtualChildId()).isEqualTo(View.NO_ID);
+ assertThat(clone.isNonVirtual()).isTrue();
+ assertThat(clone.isVirtualInt()).isFalse();
+ assertThat(clone.isVirtualLong()).isFalse();
+ assertThat(clone.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+ assertThat(clone.getVirtualChildLongId()).isEqualTo(View.NO_ID);
}
@Test
- public void testVirtual() {
+ public void testVirtual_int() {
final AutofillId id = new AutofillId(42, 108);
assertThat(id.getViewId()).isEqualTo(42);
- assertThat(id.isVirtual()).isTrue();
- assertThat(id.getVirtualChildId()).isEqualTo(108);
+ assertThat(id.isVirtualInt()).isTrue();
+ assertThat(id.isVirtualLong()).isFalse();
+ assertThat(id.isNonVirtual()).isFalse();
+ assertThat(id.getVirtualChildIntId()).isEqualTo(108);
+ assertThat(id.getVirtualChildLongId()).isEqualTo(View.NO_ID);
final AutofillId clone = cloneThroughParcel(id);
assertThat(clone.getViewId()).isEqualTo(42);
- assertThat(clone.isVirtual()).isTrue();
- assertThat(clone.getVirtualChildId()).isEqualTo(108);
+ assertThat(clone.isVirtualLong()).isFalse();
+ assertThat(clone.isVirtualInt()).isTrue();
+ assertThat(clone.isNonVirtual()).isFalse();
+ assertThat(clone.getVirtualChildIntId()).isEqualTo(108);
+ assertThat(clone.getVirtualChildLongId()).isEqualTo(View.NO_ID);
+ }
+
+ @Test
+ public void testVirtual_long() {
+ final AutofillId id = new AutofillId(new AutofillId(42), 4815162342L, 108);
+ assertThat(id.getViewId()).isEqualTo(42);
+ assertThat(id.isVirtualLong()).isTrue();
+ assertThat(id.isVirtualInt()).isFalse();
+ assertThat(id.isNonVirtual()).isFalse();
+ assertThat(id.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+ assertThat(id.getVirtualChildLongId()).isEqualTo(4815162342L);
+
+ final AutofillId clone = cloneThroughParcel(id);
+ assertThat(clone.getViewId()).isEqualTo(42);
+ assertThat(clone.isVirtualLong()).isTrue();
+ assertThat(clone.isVirtualInt()).isFalse();
+ assertThat(clone.isNonVirtual()).isFalse();
+ assertThat(clone.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+ assertThat(clone.getVirtualChildLongId()).isEqualTo(4815162342L);
}
@Test
@@ -62,27 +93,33 @@
final AutofillId id = new AutofillId(new AutofillId(42), 108);
assertThat(id.getViewId()).isEqualTo(42);
- assertThat(id.isVirtual()).isTrue();
- assertThat(id.getVirtualChildId()).isEqualTo(108);
+ assertThat(id.isVirtualInt()).isTrue();
+ assertThat(id.getVirtualChildIntId()).isEqualTo(108);
final AutofillId clone = cloneThroughParcel(id);
assertThat(clone.getViewId()).isEqualTo(42);
- assertThat(clone.isVirtual()).isTrue();
- assertThat(clone.getVirtualChildId()).isEqualTo(108);
+ assertThat(clone.isVirtualInt()).isTrue();
+ assertThat(clone.getVirtualChildIntId()).isEqualTo(108);
}
@Test
public void testVirtual_withSession() {
- final AutofillId id = new AutofillId(new AutofillId(42), 108, 666);
+ final AutofillId id = new AutofillId(new AutofillId(42), 108L, 666);
assertThat(id.getViewId()).isEqualTo(42);
- assertThat(id.isVirtual()).isTrue();
- assertThat(id.getVirtualChildId()).isEqualTo(108);
+ assertThat(id.isVirtualLong()).isTrue();
+ assertThat(id.isVirtualInt()).isFalse();
+ assertThat(id.isNonVirtual()).isFalse();
+ assertThat(id.getVirtualChildLongId()).isEqualTo(108L);
+ assertThat(id.getVirtualChildIntId()).isEqualTo(View.NO_ID);
assertThat(id.getSessionId()).isEqualTo(666);
final AutofillId clone = cloneThroughParcel(id);
assertThat(clone.getViewId()).isEqualTo(42);
- assertThat(clone.isVirtual()).isTrue();
- assertThat(clone.getVirtualChildId()).isEqualTo(108);
+ assertThat(clone.isVirtualLong()).isTrue();
+ assertThat(clone.isVirtualInt()).isFalse();
+ assertThat(clone.isNonVirtual()).isFalse();
+ assertThat(clone.getVirtualChildLongId()).isEqualTo(108L);
+ assertThat(clone.getVirtualChildIntId()).isEqualTo(View.NO_ID);
assertThat(clone.getSessionId()).isEqualTo(666);
}
@@ -118,13 +155,14 @@
assertThat(virtualIdDifferentParent).isNotEqualTo(virtualIdDifferentChild);
assertThat(virtualIdDifferentChild).isNotEqualTo(virtualIdDifferentParent);
- final AutofillId virtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108);
+ final AutofillId virtualIdDifferentSession = new AutofillId(new AutofillId(42), 1L, 108);
assertThat(virtualIdDifferentSession).isNotEqualTo(virtualId);
assertThat(virtualId).isNotEqualTo(virtualIdDifferentSession);
assertThat(virtualIdDifferentSession).isNotEqualTo(realId);
assertThat(realId).isNotEqualTo(virtualIdDifferentSession);
- final AutofillId sameVirtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108);
+ final AutofillId sameVirtualIdDifferentSession =
+ new AutofillId(new AutofillId(42), 1L, 108);
assertThat(sameVirtualIdDifferentSession).isEqualTo(virtualIdDifferentSession);
assertThat(virtualIdDifferentSession).isEqualTo(sameVirtualIdDifferentSession);
assertThat(sameVirtualIdDifferentSession.hashCode())
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
new file mode 100644
index 0000000..312e0e0
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.view.contentcapture;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Unit test for {@link ContentCaptureManager}.
+ *
+ * <p>To run it:
+ * {@code atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureManagerTest}
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ContentCaptureManagerTest {
+
+ @Mock
+ private Context mMockContext;
+
+ private ContentCaptureManager mManager;
+
+ @Before
+ public void before() {
+ mManager = new ContentCaptureManager(mMockContext, null);
+ }
+
+ @Test
+ public void testRemoveUserData_invalid() {
+ assertThrows(NullPointerException.class, () -> mManager.removeUserData(null));
+ }
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index ff97aa1..bfa6e06 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -31,7 +31,7 @@
import org.mockito.junit.MockitoJUnitRunner;
/**
- * Unit test for {@link ContentCaptureSessionTest}.
+ * Unit tests for {@link ContentCaptureSession}.
*
* <p>To run it:
* {@code atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureSessionTest}
@@ -48,17 +48,18 @@
@Test
public void testNewAutofillId_invalid() {
- assertThrows(NullPointerException.class, () -> mSession1.newAutofillId(null, 42));
+ assertThrows(NullPointerException.class, () -> mSession1.newAutofillId(null, 42L));
assertThrows(IllegalArgumentException.class,
- () -> mSession1.newAutofillId(new AutofillId(42, 42), 42));
+ () -> mSession1.newAutofillId(new AutofillId(42, 42), 42L));
}
@Test
public void testNewAutofillId_valid() {
final AutofillId parentId = new AutofillId(42);
- final AutofillId childId = mSession1.newAutofillId(parentId, 108);
+ final AutofillId childId = mSession1.newAutofillId(parentId, 108L);
assertThat(childId.getViewId()).isEqualTo(42);
- assertThat(childId.getVirtualChildId()).isEqualTo(108);
+ assertThat(childId.getVirtualChildLongId()).isEqualTo(108L);
+ assertThat(childId.getVirtualChildIntId()).isEqualTo(View.NO_ID);
assertThat(childId.getSessionId()).isEqualTo(mSession1.getIdAsInt());
}
@@ -66,8 +67,8 @@
public void testNewAutofillId_differentSessions() {
assertThat(mSession1.getIdAsInt()).isNotSameAs(mSession2.getIdAsInt()); //sanity check
final AutofillId parentId = new AutofillId(42);
- final AutofillId childId1 = mSession1.newAutofillId(parentId, 108);
- final AutofillId childId2 = mSession2.newAutofillId(parentId, 108);
+ final AutofillId childId1 = mSession1.newAutofillId(parentId, 108L);
+ final AutofillId childId2 = mSession2.newAutofillId(parentId, 108L);
assertThat(childId1).isNotEqualTo(childId2);
assertThat(childId2).isNotEqualTo(childId1);
}
@@ -91,9 +92,9 @@
@Test
public void testNewVirtualViewStructure() {
final AutofillId parentId = new AutofillId(42);
- final ViewStructure structure = mSession1.newVirtualViewStructure(parentId, 108);
+ final ViewStructure structure = mSession1.newVirtualViewStructure(parentId, 108L);
assertThat(structure).isNotNull();
- final AutofillId childId = mSession1.newAutofillId(parentId, 108);
+ final AutofillId childId = mSession1.newAutofillId(parentId, 108L);
assertThat(structure.getAutofillId()).isEqualTo(childId);
}
@@ -101,16 +102,16 @@
public void testNotifyViewsDisappeared_invalid() {
// Null parent
assertThrows(NullPointerException.class,
- () -> mSession1.notifyViewsDisappeared(null, new int[] {42}));
+ () -> mSession1.notifyViewsDisappeared(null, new long[] {42}));
// Null child
assertThrows(IllegalArgumentException.class,
() -> mSession1.notifyViewsDisappeared(new AutofillId(42), null));
// Empty child
assertThrows(IllegalArgumentException.class,
- () -> mSession1.notifyViewsDisappeared(new AutofillId(42), new int[] {}));
+ () -> mSession1.notifyViewsDisappeared(new AutofillId(42), new long[] {}));
// Virtual parent
assertThrows(IllegalArgumentException.class,
- () -> mSession1.notifyViewsDisappeared(new AutofillId(42, 108), new int[] {666}));
+ () -> mSession1.notifyViewsDisappeared(new AutofillId(42, 108), new long[] {666}));
}
// Cannot use @Spy because we need to pass the session id on constructor
diff --git a/core/tests/coretests/src/android/view/contentcapture/UserDataRemovalRequestTest.java b/core/tests/coretests/src/android/view/contentcapture/UserDataRemovalRequestTest.java
new file mode 100644
index 0000000..bebb2a8
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/UserDataRemovalRequestTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.view.contentcapture;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.net.Uri;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Unit test for {@link UserDataRemovalRequest}.
+ *
+ * <p>To run it:
+ * {@code atest FrameworksCoreTests:android.view.contentcapture.UserDataRemovalRequestTest}
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class UserDataRemovalRequestTest {
+
+ @Mock
+ private final Uri mUri = Uri.parse("content://com.example/");
+
+ private UserDataRemovalRequest.Builder mBuilder = new UserDataRemovalRequest.Builder();
+
+ @Test
+ public void testBuilder_addUri_invalid() {
+ assertThrows(NullPointerException.class, () -> mBuilder.addUri(null, false));
+ }
+
+ @Test
+ public void testBuilder_addUri_valid() {
+ assertThat(mBuilder.addUri(mUri, false)).isNotNull();
+ assertThat(mBuilder.addUri(Uri.parse("content://com.example2"), true)).isNotNull();
+ }
+
+ @Test
+ public void testBuilder_addUriAfterForEverything() {
+ assertThat(mBuilder.forEverything()).isNotNull();
+ assertThrows(IllegalStateException.class, () -> mBuilder.addUri(mUri, false));
+ }
+
+ @Test
+ public void testBuilder_forEverythingAfterAddingUri() {
+ assertThat(mBuilder.addUri(mUri, false)).isNotNull();
+ assertThrows(IllegalStateException.class, () -> mBuilder.forEverything());
+ }
+
+ @Test
+ public void testBuild_invalid() {
+ assertThrows(IllegalStateException.class, () -> mBuilder.build());
+ }
+
+ @Test
+ public void testBuild_valid() {
+ assertThat(new UserDataRemovalRequest.Builder().forEverything().build())
+ .isNotNull();
+ assertThat(new UserDataRemovalRequest.Builder().addUri(mUri, false).build())
+ .isNotNull();
+ }
+
+ @Test
+ public void testNoMoreInteractionsAfterBuild() {
+ assertThat(mBuilder.forEverything().build()).isNotNull();
+
+ assertThrows(IllegalStateException.class, () -> mBuilder.addUri(mUri, false));
+ assertThrows(IllegalStateException.class, () -> mBuilder.forEverything());
+ assertThrows(IllegalStateException.class, () -> mBuilder.build());
+
+ }
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
index eadde62..b84a098 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
@@ -42,7 +42,7 @@
import java.util.Locale;
/**
- * Unit test for {@link ViewNode}.
+ * Unit tests for {@link ViewNode}.
*
* <p>To run it: {@code atest FrameworksCoreTests:android.view.contentcapture.ViewNodeTest}
*/
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
index 7009fb2..4d78e40 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
@@ -262,6 +262,9 @@
assertEquals(
context.getString(com.android.internal.R.string.translate),
classification.getActions().get(0).getTitle());
+ Intent intent = (Intent) classification.getExtras()
+ .getParcelableArrayList(TextClassifierImpl.ACTIONS_INTENTS).get(0);
+ assertEquals(Intent.ACTION_TRANSLATE, intent.getAction());
LocaleList.setDefault(originalLocales);
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
index 0179ead..9cac7e7 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
@@ -43,6 +43,10 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
import com.android.internal.util.ArrayUtils;
import org.junit.Before;
@@ -76,13 +80,13 @@
@RunWith(AndroidJUnit4.class)
public class BatteryStatsCpuTimesTest {
@Mock
- KernelUidCpuTimeReader mKernelUidCpuTimeReader;
+ KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader;
@Mock
- KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
+ KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader;
@Mock
- KernelUidCpuActiveTimeReader mKernelUidCpuActiveTimeReader;
+ KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader;
@Mock
- KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader;
+ KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader;
@Mock
BatteryStatsImpl.UserInfoProvider mUserInfoProvider;
@Mock
@@ -98,10 +102,10 @@
mClocks = new MockClocks();
mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks)
- .setKernelUidCpuTimeReader(mKernelUidCpuTimeReader)
- .setKernelUidCpuFreqTimeReader(mKernelUidCpuFreqTimeReader)
- .setKernelUidCpuActiveTimeReader(mKernelUidCpuActiveTimeReader)
- .setKernelUidCpuClusterTimeReader(mKernelUidCpuClusterTimeReader)
+ .setKernelCpuUidUserSysTimeReader(mCpuUidUserSysTimeReader)
+ .setKernelCpuUidFreqTimeReader(mCpuUidFreqTimeReader)
+ .setKernelCpuUidActiveTimeReader(mCpuUidActiveTimeReader)
+ .setKernelCpuUidClusterTimeReader(mCpuUidClusterTimeReader)
.setUserInfoProvider(mUserInfoProvider);
}
@@ -113,21 +117,21 @@
final int numClusters = 3;
initKernelCpuSpeedReaders(numClusters);
final long[] freqs = {1, 12, 123, 12, 1234};
- when(mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile)).thenReturn(freqs);
+ when(mCpuUidFreqTimeReader.readFreqs(mPowerProfile)).thenReturn(freqs);
// RUN
mBatteryStatsImpl.updateCpuTimeLocked(false, false);
// VERIFY
assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs());
- verify(mKernelUidCpuTimeReader).readDelta(null);
- verify(mKernelUidCpuFreqTimeReader).readDelta(null);
+ verify(mCpuUidUserSysTimeReader).readDelta(null);
+ verify(mCpuUidFreqTimeReader).readDelta(null);
for (int i = 0; i < numClusters; ++i) {
verify(mKernelCpuSpeedReaders[i]).readDelta();
}
// Prepare for next test
- Mockito.reset(mUserInfoProvider, mKernelUidCpuFreqTimeReader, mKernelUidCpuTimeReader);
+ Mockito.reset(mUserInfoProvider, mCpuUidFreqTimeReader, mCpuUidUserSysTimeReader);
for (int i = 0; i < numClusters; ++i) {
Mockito.reset(mKernelCpuSpeedReaders[i]);
}
@@ -140,17 +144,18 @@
// VERIFY
verify(mUserInfoProvider).refreshUserIds();
- verify(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+ verify(mCpuUidUserSysTimeReader).readDelta(
+ any(KernelCpuUidUserSysTimeReader.Callback.class));
// perClusterTimesAvailable is called twice, once in updateCpuTimeLocked() and the other
// in readKernelUidCpuFreqTimesLocked.
- verify(mKernelUidCpuFreqTimeReader, times(2)).perClusterTimesAvailable();
- verify(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
- verify(mKernelUidCpuActiveTimeReader).readDelta(
- any(KernelUidCpuActiveTimeReader.Callback.class));
- verify(mKernelUidCpuClusterTimeReader).readDelta(
- any(KernelUidCpuClusterTimeReader.Callback.class));
- verifyNoMoreInteractions(mKernelUidCpuFreqTimeReader);
+ verify(mCpuUidFreqTimeReader, times(2)).perClusterTimesAvailable();
+ verify(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
+ verify(mCpuUidActiveTimeReader).readDelta(
+ any(KernelCpuUidActiveTimeReader.Callback.class));
+ verify(mCpuUidClusterTimeReader).readDelta(
+ any(KernelCpuUidClusterTimeReader.Callback.class));
+ verifyNoMoreInteractions(mCpuUidFreqTimeReader);
for (int i = 0; i < numClusters; ++i) {
verify(mKernelCpuSpeedReaders[i]).readDelta();
}
@@ -253,13 +258,14 @@
{12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
};
doAnswer(invocation -> {
- final KernelUidCpuTimeReader.Callback callback =
- (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+ (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+ callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
}
return null;
- }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+ }).when(mCpuUidUserSysTimeReader).readDelta(
+ any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
final SparseLongArray updatedUids = new SparseLongArray();
@@ -287,13 +293,14 @@
{9379, 3332409833484l}, {493247, 723234}, {3247819, 123348}
};
doAnswer(invocation -> {
- final KernelUidCpuTimeReader.Callback callback =
- (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+ (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuTime(testUids[i], deltasUs[i][0], deltasUs[i][1]);
+ callback.onUidCpuTime(testUids[i], deltasUs[i]);
}
return null;
- }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+ }).when(mCpuUidUserSysTimeReader).readDelta(
+ any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true);
@@ -326,13 +333,14 @@
{12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
};
doAnswer(invocation -> {
- final KernelUidCpuTimeReader.Callback callback =
- (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+ (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+ callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
}
return null;
- }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+ }).when(mCpuUidUserSysTimeReader).readDelta(
+ any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true);
@@ -350,7 +358,7 @@
assertEquals("Unexpected system cpu time for uid=" + testUids[i],
uidTimesUs[i][1], u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
}
- verify(mKernelUidCpuTimeReader).removeUid(isolatedUid);
+ verify(mCpuUidUserSysTimeReader).removeUid(isolatedUid);
// Add an isolated uid mapping and repeat the test.
@@ -361,13 +369,14 @@
{9379, 3332409833484l}, {493247, 723234}, {3247819, 123348}
};
doAnswer(invocation -> {
- final KernelUidCpuTimeReader.Callback callback =
- (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+ (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuTime(testUids[i], deltasUs[i][0], deltasUs[i][1]);
+ callback.onUidCpuTime(testUids[i], deltasUs[i]);
}
return null;
- }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+ }).when(mCpuUidUserSysTimeReader).readDelta(
+ any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true);
@@ -414,15 +423,16 @@
{12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
};
doAnswer(invocation -> {
- final KernelUidCpuTimeReader.Callback callback =
- (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+ (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+ callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
}
// And one for the invalid uid
- callback.onUidCpuTime(invalidUid, 3879, 239);
+ callback.onUidCpuTime(invalidUid, new long[]{3879, 239});
return null;
- }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+ }).when(mCpuUidUserSysTimeReader).readDelta(
+ any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true);
@@ -438,7 +448,7 @@
}
assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
mBatteryStatsImpl.getUidStats().get(invalidUid));
- verify(mKernelUidCpuTimeReader).removeUid(invalidUid);
+ verify(mCpuUidUserSysTimeReader).removeUid(invalidUid);
}
@Test
@@ -462,13 +472,14 @@
{12, 34}, {3394, 3123}, {7977, 80434}
};
doAnswer(invocation -> {
- final KernelUidCpuTimeReader.Callback callback =
- (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+ (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+ callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
}
return null;
- }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+ }).when(mCpuUidUserSysTimeReader).readDelta(
+ any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
final SparseLongArray updatedUids = new SparseLongArray();
@@ -541,14 +552,14 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -574,14 +585,14 @@
{43, 3345, 2143, 123, 4554}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+ callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, true);
@@ -624,15 +635,15 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
- when(mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()).thenReturn(true);
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
+ when(mCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(true);
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -668,14 +679,14 @@
{43, 3345, 2143, 123, 4554}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+ callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, true);
@@ -734,15 +745,15 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
- when(mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()).thenReturn(true);
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
+ when(mCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(true);
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(partialTimers, true, false);
@@ -824,14 +835,14 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -857,14 +868,14 @@
{43, 3345, 2143, 123, 4554, 9374983794839l, 979875}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+ callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, true);
@@ -901,14 +912,14 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -927,7 +938,7 @@
assertNull("Unexpected screen-off cpu times for uid=" + testUids[i],
u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED));
}
- verify(mKernelUidCpuFreqTimeReader).removeUid(isolatedUid);
+ verify(mCpuUidFreqTimeReader).removeUid(isolatedUid);
// Add an isolated uid mapping and repeat the test.
@@ -941,14 +952,14 @@
{43, 3345, 2143, 123, 4554}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+ callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -996,16 +1007,16 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
// And one for the invalid uid
- callback.onUidCpuFreqTime(invalidUid, new long[]{12, 839, 32, 34, 21});
+ callback.onUidCpuTime(invalidUid, new long[]{12, 839, 32, 34, 21});
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -1022,7 +1033,7 @@
}
assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
mBatteryStatsImpl.getUidStats().get(invalidUid));
- verify(mKernelUidCpuFreqTimeReader).removeUid(invalidUid);
+ verify(mCpuUidFreqTimeReader).removeUid(invalidUid);
}
@Test
@@ -1039,14 +1050,14 @@
});
final long[] uidTimesMs = {8000, 25000, 3000, 0, 42000};
doAnswer(invocation -> {
- final KernelUidCpuActiveTimeReader.Callback callback =
- (KernelUidCpuActiveTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidActiveTimeReader.Callback callback =
+ (KernelCpuUidActiveTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuActiveTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mKernelUidCpuActiveTimeReader).readDelta(
- any(KernelUidCpuActiveTimeReader.Callback.class));
+ }).when(mCpuUidActiveTimeReader).readDelta(
+ any(KernelCpuUidActiveTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuActiveTimesLocked(true);
@@ -1065,14 +1076,14 @@
updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
final long[] deltasMs = {43000, 3345000, 2143000, 123000, 4554000};
doAnswer(invocation -> {
- final KernelUidCpuActiveTimeReader.Callback callback =
- (KernelUidCpuActiveTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidActiveTimeReader.Callback callback =
+ (KernelCpuUidActiveTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuActiveTime(testUids[i], deltasMs[i]);
+ callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mKernelUidCpuActiveTimeReader).readDelta(
- any(KernelUidCpuActiveTimeReader.Callback.class));
+ }).when(mCpuUidActiveTimeReader).readDelta(
+ any(KernelCpuUidActiveTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuActiveTimesLocked(true);
@@ -1103,16 +1114,16 @@
});
final long[] uidTimesMs = {8000, 25000, 3000, 0, 42000};
doAnswer(invocation -> {
- final KernelUidCpuActiveTimeReader.Callback callback =
- (KernelUidCpuActiveTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidActiveTimeReader.Callback callback =
+ (KernelCpuUidActiveTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuActiveTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
// And one for the invalid uid
- callback.onUidCpuActiveTime(invalidUid, 1200L);
+ callback.onUidCpuTime(invalidUid, 1200L);
return null;
- }).when(mKernelUidCpuActiveTimeReader).readDelta(
- any(KernelUidCpuActiveTimeReader.Callback.class));
+ }).when(mCpuUidActiveTimeReader).readDelta(
+ any(KernelCpuUidActiveTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuActiveTimesLocked(true);
@@ -1126,7 +1137,7 @@
}
assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
mBatteryStatsImpl.getUidStats().get(invalidUid));
- verify(mKernelUidCpuActiveTimeReader).removeUid(invalidUid);
+ verify(mCpuUidActiveTimeReader).removeUid(invalidUid);
}
@Test
@@ -1147,14 +1158,14 @@
{8000, 0}
};
doAnswer(invocation -> {
- final KernelUidCpuClusterTimeReader.Callback callback =
- (KernelUidCpuClusterTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidClusterTimeReader.Callback callback =
+ (KernelCpuUidClusterTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuPolicyTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mKernelUidCpuClusterTimeReader).readDelta(
- any(KernelUidCpuClusterTimeReader.Callback.class));
+ }).when(mCpuUidClusterTimeReader).readDelta(
+ any(KernelCpuUidClusterTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuClusterTimesLocked(true);
@@ -1177,14 +1188,14 @@
{43000, 3345000}
};
doAnswer(invocation -> {
- final KernelUidCpuClusterTimeReader.Callback callback =
- (KernelUidCpuClusterTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidClusterTimeReader.Callback callback =
+ (KernelCpuUidClusterTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuPolicyTime(testUids[i], deltasMs[i]);
+ callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mKernelUidCpuClusterTimeReader).readDelta(
- any(KernelUidCpuClusterTimeReader.Callback.class));
+ }).when(mCpuUidClusterTimeReader).readDelta(
+ any(KernelCpuUidClusterTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuClusterTimesLocked(true);
@@ -1193,7 +1204,8 @@
for (int i = 0; i < testUids.length; ++i) {
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
assertNotNull("No entry for uid=" + testUids[i], u);
- assertArrayEquals("Unexpected cpu cluster time for uid=" + testUids[i], sum(uidTimesMs[i], deltasMs[i]),
+ assertArrayEquals("Unexpected cpu cluster time for uid=" + testUids[i],
+ sum(uidTimesMs[i], deltasMs[i]),
u.getCpuClusterTimes());
}
}
@@ -1219,16 +1231,16 @@
{8000, 0}
};
doAnswer(invocation -> {
- final KernelUidCpuClusterTimeReader.Callback callback =
- (KernelUidCpuClusterTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidClusterTimeReader.Callback callback =
+ (KernelCpuUidClusterTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuPolicyTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
// And one for the invalid uid
- callback.onUidCpuPolicyTime(invalidUid, new long[] {400, 1000});
+ callback.onUidCpuTime(invalidUid, new long[]{400, 1000});
return null;
- }).when(mKernelUidCpuClusterTimeReader).readDelta(
- any(KernelUidCpuClusterTimeReader.Callback.class));
+ }).when(mCpuUidClusterTimeReader).readDelta(
+ any(KernelCpuUidClusterTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuClusterTimesLocked(true);
@@ -1242,7 +1254,7 @@
}
assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
mBatteryStatsImpl.getUidStats().get(invalidUid));
- verify(mKernelUidCpuClusterTimeReader).removeUid(invalidUid);
+ verify(mCpuUidClusterTimeReader).removeUid(invalidUid);
}
@Test
@@ -1268,25 +1280,25 @@
mClocks.realtime = mClocks.uptime = 400_000;
mBatteryStatsImpl.clearPendingRemovedUids();
assertEquals(1, mBatteryStatsImpl.getPendingRemovedUids().size());
- verify(mKernelUidCpuActiveTimeReader).removeUid(1);
- verify(mKernelUidCpuActiveTimeReader).removeUidsInRange(5, 10);
- verify(mKernelUidCpuClusterTimeReader).removeUid(1);
- verify(mKernelUidCpuClusterTimeReader).removeUidsInRange(5, 10);
- verify(mKernelUidCpuFreqTimeReader).removeUid(1);
- verify(mKernelUidCpuFreqTimeReader).removeUidsInRange(5, 10);
- verify(mKernelUidCpuTimeReader).removeUid(1);
- verify(mKernelUidCpuTimeReader).removeUidsInRange(5, 10);
+ verify(mCpuUidActiveTimeReader).removeUid(1);
+ verify(mCpuUidActiveTimeReader).removeUidsInRange(5, 10);
+ verify(mCpuUidClusterTimeReader).removeUid(1);
+ verify(mCpuUidClusterTimeReader).removeUidsInRange(5, 10);
+ verify(mCpuUidFreqTimeReader).removeUid(1);
+ verify(mCpuUidFreqTimeReader).removeUidsInRange(5, 10);
+ verify(mCpuUidUserSysTimeReader).removeUid(1);
+ verify(mCpuUidUserSysTimeReader).removeUidsInRange(5, 10);
mClocks.realtime = mClocks.uptime = 800_000;
mBatteryStatsImpl.clearPendingRemovedUids();
assertEquals(0, mBatteryStatsImpl.getPendingRemovedUids().size());
- verify(mKernelUidCpuActiveTimeReader).removeUid(100);
- verify(mKernelUidCpuClusterTimeReader).removeUid(100);
- verify(mKernelUidCpuFreqTimeReader).removeUid(100);
- verify(mKernelUidCpuTimeReader).removeUid(100);
+ verify(mCpuUidActiveTimeReader).removeUid(100);
+ verify(mCpuUidClusterTimeReader).removeUid(100);
+ verify(mCpuUidFreqTimeReader).removeUid(100);
+ verify(mCpuUidUserSysTimeReader).removeUid(100);
- verifyNoMoreInteractions(mKernelUidCpuActiveTimeReader, mKernelUidCpuClusterTimeReader,
- mKernelUidCpuFreqTimeReader, mKernelUidCpuTimeReader);
+ verifyNoMoreInteractions(mCpuUidActiveTimeReader, mCpuUidClusterTimeReader,
+ mCpuUidFreqTimeReader, mCpuUidUserSysTimeReader);
}
private void updateTimeBasesLocked(boolean unplugged, int screenState,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
index dc93675..0771829 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -39,6 +39,7 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
import com.android.internal.util.ArrayUtils;
import org.junit.Before;
@@ -54,7 +55,7 @@
@RunWith(AndroidJUnit4.class)
public class BatteryStatsImplTest {
@Mock
- private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
+ private KernelCpuUidFreqTimeReader mKernelUidCpuFreqTimeReader;
@Mock
private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
@@ -67,7 +68,7 @@
when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
mBatteryStatsImpl = new MockBatteryStatsImpl()
- .setKernelUidCpuFreqTimeReader(mKernelUidCpuFreqTimeReader)
+ .setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader)
.setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index d69e1d1..a6329298 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -38,7 +38,6 @@
BatteryStatsTimerTest.class,
BatteryStatsUidTest.class,
BatteryStatsUserLifecycleTests.class,
- KernelCpuProcReaderTest.class,
KernelCpuProcStringReaderTest.class,
KernelCpuUidActiveTimeReaderTest.class,
KernelCpuUidClusterTimeReaderTest.class,
@@ -46,9 +45,6 @@
KernelCpuUidUserSysTimeReaderTest.class,
KernelMemoryBandwidthStatsTest.class,
KernelSingleUidTimeReaderTest.class,
- KernelUidCpuFreqTimeReaderTest.class,
- KernelUidCpuActiveTimeReaderTest.class,
- KernelUidCpuClusterTimeReaderTest.class,
KernelWakelockReaderTest.class,
LongSamplingCounterTest.class,
LongSamplingCounterArrayTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java
deleted file mode 100644
index a25a7489..0000000
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.os.FileUtils;
-import android.os.SystemClock;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.file.Files;
-import java.util.Arrays;
-import java.util.Random;
-
-/**
- * Test class for {@link KernelCpuProcReader}.
- *
- * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuProcReaderTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class KernelCpuProcReaderTest {
-
- private File mRoot;
- private File mTestDir;
- private File mTestFile;
- private Random mRand = new Random();
-
- private KernelCpuProcReader mKernelCpuProcReader;
-
- private Context getContext() {
- return InstrumentationRegistry.getContext();
- }
-
- @Before
- public void setUp() {
- mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
- mRoot = getContext().getFilesDir();
- mTestFile = new File(mTestDir, "test.file");
- mKernelCpuProcReader = new KernelCpuProcReader(mTestFile.getAbsolutePath());
- }
-
- @After
- public void tearDown() throws Exception {
- FileUtils.deleteContents(mTestDir);
- FileUtils.deleteContents(mRoot);
- }
-
-
- /**
- * Tests that reading will return null if the file does not exist.
- */
- @Test
- public void testReadInvalidFile() throws Exception {
- assertEquals(null, mKernelCpuProcReader.readBytes());
- }
-
- /**
- * Tests that reading will always return null after 5 failures.
- */
- @Test
- public void testReadErrorsLimit() throws Exception {
- mKernelCpuProcReader.setThrottleInterval(0);
- for (int i = 0; i < 3; i++) {
- assertNull(mKernelCpuProcReader.readBytes());
- SystemClock.sleep(50);
- }
-
- final byte[] data = new byte[1024];
- mRand.nextBytes(data);
- try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
- os.write(data);
- }
- assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
-
- assertTrue(mTestFile.delete());
- for (int i = 0; i < 3; i++) {
- assertNull(mKernelCpuProcReader.readBytes());
- SystemClock.sleep(50);
- }
- try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
- os.write(data);
- }
- assertNull(mKernelCpuProcReader.readBytes());
- }
-
- /**
- * Tests reading functionality.
- */
- @Test
- public void testSimpleRead() throws Exception {
- final byte[] data = new byte[1024];
- mRand.nextBytes(data);
- try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
- os.write(data);
- }
- assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
- }
-
- /**
- * Tests multiple reading functionality.
- */
- @Test
- public void testMultipleRead() throws Exception {
- mKernelCpuProcReader.setThrottleInterval(0);
- for (int i = 0; i < 100; i++) {
- final byte[] data = new byte[mRand.nextInt(102400) + 4];
- mRand.nextBytes(data);
- try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
- os.write(data);
- }
- assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
- assertTrue(mTestFile.delete());
- }
- }
-
- /**
- * Tests reading with resizing.
- */
- @Test
- public void testReadWithResize() throws Exception {
- final byte[] data = new byte[128001];
- mRand.nextBytes(data);
- try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
- os.write(data);
- }
- assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
- }
-
- /**
- * Tests that reading a file over the limit (1MB) will return null.
- */
- @Test
- public void testReadOverLimit() throws Exception {
- final byte[] data = new byte[1228800];
- mRand.nextBytes(data);
- try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
- os.write(data);
- }
- assertNull(mKernelCpuProcReader.readBytes());
- }
-
- /**
- * Tests throttling. Deleting underlying file should not affect cache.
- */
- @Test
- public void testThrottle() throws Exception {
- mKernelCpuProcReader.setThrottleInterval(3000);
- final byte[] data = new byte[20001];
- mRand.nextBytes(data);
- try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
- os.write(data);
- }
- assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
- assertTrue(mTestFile.delete());
- for (int i = 0; i < 5; i++) {
- assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
- SystemClock.sleep(10);
- }
- SystemClock.sleep(5000);
- assertNull(mKernelCpuProcReader.readBytes());
- }
-
- private byte[] toArray(ByteBuffer buffer) {
- assertNotNull(buffer);
- byte[] arr = new byte[buffer.remaining()];
- buffer.get(arr);
- return arr;
- }
-}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
index 7a31605..cbd2ba4 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
@@ -299,9 +299,10 @@
assertTrue(mTestFile.delete());
try (BufferedWriter w = Files.newBufferedWriter(mTestFile.toPath())) {
w.write(data1);
- modify.countDown();
} catch (Throwable e) {
errs.add(e);
+ } finally {
+ modify.countDown();
}
}, 600, TimeUnit.MILLISECONDS);
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java
deleted file mode 100644
index 12f6c18..0000000
--- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (C) 2017 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.internal.os;
-
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Random;
-
-/**
- * Test class for {@link KernelUidCpuActiveTimeReader}.
- *
- * To run it:
- * bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuActiveTimeReaderTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class KernelUidCpuActiveTimeReaderTest {
- @Mock
- private KernelCpuProcReader mProcReader;
- @Mock
- private KernelUidCpuActiveTimeReader.Callback mCallback;
- private KernelUidCpuActiveTimeReader mReader;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mReader = new KernelUidCpuActiveTimeReader(mProcReader);
- mReader.setThrottleInterval(0);
- }
-
- @Test
- public void testReadDelta() {
- final int cores = 8;
- final int[] uids = {1, 22, 333, 4444, 5555};
-
- final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length; i++) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i]));
- }
- verifyNoMoreInteractions(mCallback);
-
- // Verify that a second call will only return deltas.
- Mockito.reset(mCallback);
- final long[][] times1 = increaseTime(times);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length; i++) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times1[i], times[i])));
- }
- verifyNoMoreInteractions(mCallback);
-
- // Verify that there won't be a callback if the proc file values didn't change.
- Mockito.reset(mCallback);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1));
- mReader.readDelta(mCallback);
- verifyNoMoreInteractions(mCallback);
-
- // Verify that calling with a null callback doesn't result in any crashes
- Mockito.reset(mCallback);
- final long[][] times2 = increaseTime(times1);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times2));
- mReader.readDelta(null);
-
- // Verify that the readDelta call will only return deltas when
- // the previous call had null callback.
- Mockito.reset(mCallback);
- final long[][] times3 = increaseTime(times2);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length; ++i) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times3[i], times2[i])));
- }
- verifyNoMoreInteractions(mCallback);
- }
-
- @Test
- public void testReadAbsolute() {
- final int cores = 8;
- final int[] uids = {1, 22, 333, 4444, 5555};
-
- final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
- mReader.readAbsolute(mCallback);
- for (int i = 0; i < uids.length; i++) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i]));
- }
- verifyNoMoreInteractions(mCallback);
-
- // Verify that a second call still returns absolute values
- Mockito.reset(mCallback);
- final long[][] times1 = increaseTime(times);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1));
- mReader.readAbsolute(mCallback);
- for (int i = 0; i < uids.length; i++) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times1[i]));
- }
- verifyNoMoreInteractions(mCallback);
- }
-
- @Test
- public void testReadDelta_malformedData() {
- final int cores = 8;
- final int[] uids = {1, 22, 333, 4444, 5555};
- final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length; i++) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i]));
- }
- verifyNoMoreInteractions(mCallback);
-
- // Verify that there is no callback if subsequent call is in wrong format.
- Mockito.reset(mCallback);
- final long[][] times1 = increaseTime(times);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1).putInt(0, 5));
- mReader.readDelta(mCallback);
- verifyNoMoreInteractions(mCallback);
-
- // Verify that the internal state was not modified if the given core count does not match
- // the following # of entries.
- Mockito.reset(mCallback);
- final long[][] times2 = increaseTime(times);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times2));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length; i++) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times2[i], times[i])));
- }
- verifyNoMoreInteractions(mCallback);
-
- // Verify that there is no callback if any value in the proc file is -ve.
- Mockito.reset(mCallback);
- final long[][] times3 = increaseTime(times2);
- times3[uids.length - 1][cores - 1] *= -1;
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length - 1; ++i) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times3[i], times2[i])));
- }
- verifyNoMoreInteractions(mCallback);
-
- // Verify that the internal state was not modified when the proc file had -ve value.
- Mockito.reset(mCallback);
- for (int i = 0; i < cores; i++) {
- times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 2520;
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3));
- mReader.readDelta(mCallback);
- verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1],
- getTotal(subtract(times3[uids.length - 1], times2[uids.length - 1])));
- verifyNoMoreInteractions(mCallback);
-
- // Verify that there is no callback if the values in the proc file are decreased.
- Mockito.reset(mCallback);
- final long[][] times4 = increaseTime(times3);
- System.arraycopy(times3[uids.length - 1], 0, times4[uids.length - 1], 0, cores);
- times4[uids.length - 1][cores - 1] -= 100;
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times4));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length - 1; ++i) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times4[i], times3[i])));
- }
- verifyNoMoreInteractions(mCallback);
-
- // Verify that the internal state was not modified when the proc file had decreased values.
- Mockito.reset(mCallback);
- for (int i = 0; i < cores; i++) {
- times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 2520;
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times4));
- mReader.readDelta(mCallback);
- verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1],
- getTotal(subtract(times4[uids.length - 1], times3[uids.length - 1])));
- verifyNoMoreInteractions(mCallback);
- }
-
- private long[] subtract(long[] a1, long[] a2) {
- long[] val = new long[a1.length];
- for (int i = 0; i < val.length; ++i) {
- val[i] = a1[i] - a2[i];
- }
- return val;
- }
-
- /**
- * Unit of original and return value is 10ms. What's special about 2520? 2520 is LCM of 1, 2, 3,
- * ..., 10. So that when wedivide shared cpu time by concurrent thread count, we always get a
- * nice integer, avoiding rounding errors.
- */
- private long[][] increaseTime(long[][] original) {
- long[][] newTime = new long[original.length][original[0].length];
- Random rand = new Random();
- for (int i = 0; i < original.length; i++) {
- for (int j = 0; j < original[0].length; j++) {
- newTime[i][j] = original[i][j] + rand.nextInt(1000) * 2520 + 2520;
- }
- }
- return newTime;
- }
-
- // Unit of times is 10ms
- private long getTotal(long[] times) {
- long sum = 0;
- for (int i = 0; i < times.length; i++) {
- sum += times[i] * 10 / (i + 1);
- }
- return sum;
- }
-
- /**
- * Format uids and times (in 10ms) into the following format:
- * [n, uid0, time0a, time0b, ..., time0n,
- * uid1, time1a, time1b, ..., time1n,
- * uid2, time2a, time2b, ..., time2n, etc.]
- * where n is the total number of cpus (num_possible_cpus)
- */
- private ByteBuffer getUidTimesBytes(int[] uids, long[][] times) {
- int size = (1 + uids.length * (times[0].length + 1)) * 4;
- ByteBuffer buf = ByteBuffer.allocate(size);
- buf.order(ByteOrder.nativeOrder());
- buf.putInt(times[0].length);
- for (int i = 0; i < uids.length; i++) {
- buf.putInt(uids[i]);
- for (int j = 0; j < times[i].length; j++) {
- buf.putInt((int) times[i][j]);
- }
- }
- buf.flip();
- return buf.order(ByteOrder.nativeOrder());
- }
-}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java
deleted file mode 100644
index 532f337..0000000
--- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2017 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.internal.os;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.Mockito.when;
-
-import android.util.SparseArray;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-import java.util.Random;
-
-/**
- * Test class for {@link KernelUidCpuClusterTimeReader}.
- *
- * To run it:
- * bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuClusterTimeReaderTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class KernelUidCpuClusterTimeReaderTest {
- @Mock
- private KernelCpuProcReader mProcReader;
- private KernelUidCpuClusterTimeReader mReader;
- private VerifiableCallback mCallback;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mReader = new KernelUidCpuClusterTimeReader(mProcReader);
- mCallback = new VerifiableCallback();
- mReader.setThrottleInterval(0);
- }
-
- @Test
- public void testReadDelta() throws Exception {
- VerifiableCallback cb = new VerifiableCallback();
- final int cores = 6;
- final int[] clusters = {2, 4};
- final int[] uids = {1, 22, 333, 4444, 5555};
-
- // Verify initial call
- final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
- mReader.readDelta(cb);
- for (int i = 0; i < uids.length; i++) {
- cb.verify(uids[i], getTotal(clusters, times[i]));
- }
- cb.verifyNoMoreInteractions();
-
- // Verify that a second call will only return deltas.
- cb.clear();
- Mockito.reset(mProcReader);
- final long[][] times1 = increaseTime(times);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
- mReader.readDelta(cb);
- for (int i = 0; i < uids.length; i++) {
- cb.verify(uids[i], getTotal(clusters, subtract(times1[i], times[i])));
- }
- cb.verifyNoMoreInteractions();
-
- // Verify that there won't be a callback if the proc file values didn't change.
- cb.clear();
- Mockito.reset(mProcReader);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
- mReader.readDelta(cb);
- cb.verifyNoMoreInteractions();
-
- // Verify that calling with a null callback doesn't result in any crashes
- Mockito.reset(mProcReader);
- final long[][] times2 = increaseTime(times1);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2));
- mReader.readDelta(null);
-
- // Verify that the readDelta call will only return deltas when
- // the previous call had null callback.
- cb.clear();
- Mockito.reset(mProcReader);
- final long[][] times3 = increaseTime(times2);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
- mReader.readDelta(cb);
- for (int i = 0; i < uids.length; i++) {
- cb.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i])));
- }
- cb.verifyNoMoreInteractions();
-
- }
-
- @Test
- public void testReadAbsolute() throws Exception {
- VerifiableCallback cb = new VerifiableCallback();
- final int cores = 6;
- final int[] clusters = {2, 4};
- final int[] uids = {1, 22, 333, 4444, 5555};
-
- // Verify return absolute value
- final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
- mReader.readAbsolute(cb);
- for (int i = 0; i < uids.length; i++) {
- cb.verify(uids[i], getTotal(clusters, times[i]));
- }
- cb.verifyNoMoreInteractions();
-
- // Verify that a second call should return the same absolute value
- cb.clear();
- Mockito.reset(mProcReader);
- final long[][] times1 = increaseTime(times);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
- mReader.readAbsolute(cb);
- for (int i = 0; i < uids.length; i++) {
- cb.verify(uids[i], getTotal(clusters, times1[i]));
- }
- cb.verifyNoMoreInteractions();
- }
-
- @Test
- public void testReadDelta_malformedData() throws Exception {
- final int cores = 6;
- final int[] clusters = {2, 4};
- final int[] uids = {1, 22, 333, 4444, 5555};
-
- // Verify initial call
- final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length; i++) {
- mCallback.verify(uids[i], getTotal(clusters, times[i]));
- }
- mCallback.verifyNoMoreInteractions();
-
- // Verify that there is no callback if a call has wrong format
- mCallback.clear();
- Mockito.reset(mProcReader);
- final long[][] temp = increaseTime(times);
- final long[][] times1 = new long[uids.length][];
- for (int i = 0; i < temp.length; i++) {
- times1[i] = Arrays.copyOfRange(temp[i], 0, 4);
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
- mReader.readDelta(mCallback);
- mCallback.verifyNoMoreInteractions();
-
- // Verify that the internal state was not modified if the given core count does not match
- // the following # of entries.
- mCallback.clear();
- Mockito.reset(mProcReader);
- final long[][] times2 = increaseTime(times);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length; i++) {
- mCallback.verify(uids[i], getTotal(clusters, subtract(times2[i], times[i])));
- }
- mCallback.verifyNoMoreInteractions();
-
- // Verify that there is no callback if any value in the proc file is -ve.
- mCallback.clear();
- Mockito.reset(mProcReader);
- final long[][] times3 = increaseTime(times2);
- times3[uids.length - 1][cores - 1] *= -1;
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length - 1; i++) {
- mCallback.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i])));
- }
- mCallback.verifyNoMoreInteractions();
-
- // Verify that the internal state was not modified when the proc file had -ve value.
- mCallback.clear();
- Mockito.reset(mProcReader);
- for (int i = 0; i < cores; i++) {
- times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 2520;
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
- mReader.readDelta(mCallback);
- mCallback.verify(uids[uids.length - 1],
- getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1])));
-
- // Verify that there is no callback if the values in the proc file are decreased.
- mCallback.clear();
- Mockito.reset(mProcReader);
- final long[][] times4 = increaseTime(times3);
- System.arraycopy(times3[uids.length - 1], 0, times4[uids.length - 1], 0, cores);
- times4[uids.length - 1][cores - 1] -= 100;
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length - 1; i++) {
- mCallback.verify(uids[i], getTotal(clusters, subtract(times4[i], times3[i])));
- }
- mCallback.verifyNoMoreInteractions();
-
- // Verify that the internal state was not modified when the proc file had decreased values.
- mCallback.clear();
- Mockito.reset(mProcReader);
- for (int i = 0; i < cores; i++) {
- times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 2520;
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4));
- mReader.readDelta(mCallback);
- mCallback.verify(uids[uids.length - 1],
- getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1])));
- mCallback.verifyNoMoreInteractions();
- }
-
-
- private long[] subtract(long[] a1, long[] a2) {
- long[] val = new long[a1.length];
- for (int i = 0; i < val.length; ++i) {
- val[i] = a1[i] - a2[i];
- }
- return val;
- }
-
- /**
- * Unit is 10ms. What's special about 2520? 2520 is LCM of 1, 2, 3, ..., 10. So that when we
- * divide shared cpu time by concurrent thread count, we always get a nice integer, avoiding
- * rounding errors.
- */
- private long[][] increaseTime(long[][] original) {
- long[][] newTime = new long[original.length][original[0].length];
- Random rand = new Random();
- for (int i = 0; i < original.length; i++) {
- for (int j = 0; j < original[0].length; j++) {
- newTime[i][j] = original[i][j] + rand.nextInt(1000) * 2520 + 2520;
- }
- }
- return newTime;
- }
-
- // Format an array of cluster times according to the algorithm in KernelUidCpuClusterTimeReader
- private long[] getTotal(int[] cluster, long[] times) {
- int core = 0;
- long[] sumTimes = new long[cluster.length];
- for (int i = 0; i < cluster.length; i++) {
- double sum = 0;
- for (int j = 0; j < cluster[i]; j++) {
- sum += (double) times[core++] * 10 / (j + 1);
- }
- sumTimes[i] = (long) sum;
- }
- return sumTimes;
- }
-
- private class VerifiableCallback implements KernelUidCpuClusterTimeReader.Callback {
-
- SparseArray<long[]> mData = new SparseArray<>();
- int count = 0;
-
- public void verify(int uid, long[] cpuClusterTimeMs) {
- long[] array = mData.get(uid);
- assertNotNull(array);
- assertArrayEquals(cpuClusterTimeMs, array);
- count++;
- }
-
- public void clear() {
- mData.clear();
- count = 0;
- }
-
- @Override
- public void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs) {
- long[] array = new long[cpuClusterTimeMs.length];
- System.arraycopy(cpuClusterTimeMs, 0, array, 0, array.length);
- mData.put(uid, array);
- }
-
- public void verifyNoMoreInteractions() {
- assertEquals(mData.size(), count);
- }
- }
-
- /**
- * Format uids and times (in 10ms) into the following format:
- * [n, x0, ..., xn, uid0, time0a, time0b, ..., time0n,
- * uid1, time1a, time1b, ..., time1n,
- * uid2, time2a, time2b, ..., time2n, etc.]
- * where n is the number of policies
- * xi is the number cpus on a particular policy
- */
- private ByteBuffer getUidTimesBytes(int[] uids, int[] clusters, long[][] times) {
- int size = (1 + clusters.length + uids.length * (times[0].length + 1)) * 4;
- ByteBuffer buf = ByteBuffer.allocate(size);
- buf.order(ByteOrder.nativeOrder());
- buf.putInt(clusters.length);
- for (int i = 0; i < clusters.length; i++) {
- buf.putInt(clusters[i]);
- }
- for (int i = 0; i < uids.length; i++) {
- buf.putInt(uids[i]);
- for (int j = 0; j < times[i].length; j++) {
- buf.putInt((int) (times[i][j]));
- }
- }
- buf.flip();
- return buf.order(ByteOrder.nativeOrder());
- }
-}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java
deleted file mode 100644
index 6d2980b..0000000
--- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * Copyright (C) 2017 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.internal.os;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.util.SparseArray;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.BufferedReader;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-
-/**
- * Test class for {@link KernelUidCpuFreqTimeReader}.
- *
- * To run the tests, use
- *
- * runtest -c com.android.internal.os.KernelUidCpuFreqTimeReaderTest frameworks-core
- *
- * or the following steps:
- *
- * Build: m FrameworksCoreTests
- * Install: adb install -r \
- * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
- * Run: adb shell am instrument -e class com.android.internal.os.KernelUidCpuFreqTimeReaderTest -w \
- * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
- *
- * or
- *
- * bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuFreqTimeReaderTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class KernelUidCpuFreqTimeReaderTest {
- @Mock
- private BufferedReader mBufferedReader;
- @Mock
- private KernelUidCpuFreqTimeReader.Callback mCallback;
- @Mock
- private PowerProfile mPowerProfile;
- @Mock
- private KernelCpuProcReader mProcReader;
-
- private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mKernelUidCpuFreqTimeReader = new KernelUidCpuFreqTimeReader(mProcReader);
- mKernelUidCpuFreqTimeReader.setThrottleInterval(0);
- }
-
- @Test
- public void testReadFreqs_perClusterTimesNotAvailable() throws Exception {
- final long[][] freqs = {
- {1, 12, 123, 1234},
- {1, 12, 123, 23, 123, 1234, 12345, 123456},
- {1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345},
- {1, 12, 123, 23, 2345, 234567}
- };
- final int[] numClusters = {2, 2, 3, 1};
- final int[][] numFreqs = {{3, 6}, {4, 5}, {3, 5, 4}, {3}};
- for (int i = 0; i < freqs.length; ++i) {
- setCpuClusterFreqs(numClusters[i], numFreqs[i]);
- when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs[i]));
- long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(
- mBufferedReader, mPowerProfile);
- assertArrayEquals(freqs[i], actualFreqs);
- verifyZeroInteractions(mCallback);
- final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
- Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
- assertFalse(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable());
-
- // Verify that a second call won't read the proc file again
- Mockito.reset(mBufferedReader);
- actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
- assertArrayEquals(freqs[i], actualFreqs);
- assertFalse(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable());
-
- // Prepare for next iteration
- Mockito.reset(mBufferedReader, mPowerProfile);
- }
- }
-
- @Test
- public void testReadFreqs_perClusterTimesAvailable() throws Exception {
- final long[][] freqs = {
- {1, 12, 123, 1234},
- {1, 12, 123, 23, 123, 1234, 12345, 123456},
- {1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345, 1234567}
- };
- final int[] numClusters = {1, 2, 3};
- final int[][] numFreqs = {{4}, {3, 5}, {3, 5, 4}};
- for (int i = 0; i < freqs.length; ++i) {
- setCpuClusterFreqs(numClusters[i], numFreqs[i]);
- when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs[i]));
- long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(
- mBufferedReader, mPowerProfile);
- assertArrayEquals(freqs[i], actualFreqs);
- verifyZeroInteractions(mCallback);
- final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
- Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
- assertTrue(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable());
-
- // Verify that a second call won't read the proc file again
- Mockito.reset(mBufferedReader);
- actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
- assertArrayEquals(freqs[i], actualFreqs);
- assertTrue(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable());
-
- // Prepare for next iteration
- Mockito.reset(mBufferedReader, mPowerProfile);
- }
- }
-
- @Test
- public void testReadDelta_Binary() throws Exception {
- VerifiableCallback cb = new VerifiableCallback();
- final long[] freqs = {110, 123, 145, 167, 289, 997};
- final int[] uids = {1, 22, 333, 444, 555};
- final long[][] times = new long[uids.length][freqs.length];
- for (int i = 0; i < uids.length; ++i) {
- for (int j = 0; j < freqs.length; ++j) {
- times[i][j] = uids[i] * freqs[j] * 10;
- }
- }
- when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs));
- long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mBufferedReader, mPowerProfile);
-
- assertArrayEquals(freqs, actualFreqs);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
- mKernelUidCpuFreqTimeReader.readDeltaImpl(cb);
- for (int i = 0; i < uids.length; ++i) {
- cb.verify(uids[i], times[i]);
- }
- cb.verifyNoMoreInteractions();
-
- // Verify that a second call will only return deltas.
- cb.clear();
- Mockito.reset(mProcReader);
- final long[][] newTimes1 = new long[uids.length][freqs.length];
- for (int i = 0; i < uids.length; ++i) {
- for (int j = 0; j < freqs.length; ++j) {
- newTimes1[i][j] = times[i][j] + (uids[i] + freqs[j]) * 50;
- }
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1));
- mKernelUidCpuFreqTimeReader.readDeltaImpl(cb);
- for (int i = 0; i < uids.length; ++i) {
- cb.verify(uids[i], subtract(newTimes1[i], times[i]));
- }
- cb.verifyNoMoreInteractions();
-
- // Verify that there won't be a callback if the proc file values didn't change.
- cb.clear();
- Mockito.reset(mProcReader);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1));
- mKernelUidCpuFreqTimeReader.readDeltaImpl(cb);
- cb.verifyNoMoreInteractions();
-
- // Verify that calling with a null callback doesn't result in any crashes
- cb.clear();
- Mockito.reset(mProcReader);
- final long[][] newTimes2 = new long[uids.length][freqs.length];
- for (int i = 0; i < uids.length; ++i) {
- for (int j = 0; j < freqs.length; ++j) {
- newTimes2[i][j] = newTimes1[i][j] + (uids[i] * freqs[j]) * 30;
- }
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes2));
- mKernelUidCpuFreqTimeReader.readDeltaImpl(null);
- cb.verifyNoMoreInteractions();
-
- // Verify that the readDelta call will only return deltas when
- // the previous call had null callback.
- cb.clear();
- Mockito.reset(mProcReader);
- final long[][] newTimes3 = new long[uids.length][freqs.length];
- for (int i = 0; i < uids.length; ++i) {
- for (int j = 0; j < freqs.length; ++j) {
- newTimes3[i][j] = newTimes2[i][j] + (uids[i] + freqs[j]) * 40;
- }
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes3));
- mKernelUidCpuFreqTimeReader.readDeltaImpl(cb);
- for (int i = 0; i < uids.length; ++i) {
- cb.verify(uids[i], subtract(newTimes3[i], newTimes2[i]));
- }
- cb.verifyNoMoreInteractions();
- }
-
- @Test
- public void testReadAbsolute() throws Exception {
- VerifiableCallback cb = new VerifiableCallback();
- final long[] freqs = {110, 123, 145, 167, 289, 997};
- final int[] uids = {1, 22, 333, 444, 555};
- final long[][] times = new long[uids.length][freqs.length];
- for (int i = 0; i < uids.length; ++i) {
- for (int j = 0; j < freqs.length; ++j) {
- times[i][j] = uids[i] * freqs[j] * 10;
- }
- }
- when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs));
- long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mBufferedReader, mPowerProfile);
-
- assertArrayEquals(freqs, actualFreqs);
- // Verify that the absolute values are returned
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
- mKernelUidCpuFreqTimeReader.readAbsolute(cb);
- for (int i = 0; i < uids.length; ++i) {
- cb.verify(uids[i], times[i]);
- }
- cb.verifyNoMoreInteractions();
-
- // Verify that a second call should still return absolute values
- cb.clear();
- Mockito.reset(mProcReader);
- final long[][] newTimes1 = new long[uids.length][freqs.length];
- for (int i = 0; i < uids.length; ++i) {
- for (int j = 0; j < freqs.length; ++j) {
- newTimes1[i][j] = times[i][j] + (uids[i] + freqs[j]) * 50;
- }
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1));
- mKernelUidCpuFreqTimeReader.readAbsolute(cb);
- for (int i = 0; i < uids.length; ++i) {
- cb.verify(uids[i], newTimes1[i]);
- }
- cb.verifyNoMoreInteractions();
- }
-
- private long[] subtract(long[] a1, long[] a2) {
- long[] val = new long[a1.length];
- for (int i = 0; i < val.length; ++i) {
- val[i] = a1[i] - a2[i];
- }
- return val;
- }
-
- private String getFreqsLine(long[] freqs) {
- final StringBuilder sb = new StringBuilder();
- sb.append("uid:");
- for (int i = 0; i < freqs.length; ++i) {
- sb.append(" " + freqs[i]);
- }
- return sb.toString();
- }
-
- private ByteBuffer getUidTimesBytes(int[] uids, long[][] times) {
- int size = (1 + uids.length + uids.length * times[0].length) * 4;
- ByteBuffer buf = ByteBuffer.allocate(size);
- buf.order(ByteOrder.nativeOrder());
- buf.putInt(times[0].length);
- for (int i = 0; i < uids.length; i++) {
- buf.putInt(uids[i]);
- for (int j = 0; j < times[i].length; j++) {
- buf.putInt((int) (times[i][j] / 10));
- }
- }
- buf.flip();
- return buf.asReadOnlyBuffer().order(ByteOrder.nativeOrder());
- }
-
- private void setCpuClusterFreqs(int numClusters, int... clusterFreqs) {
- assertEquals(numClusters, clusterFreqs.length);
- when(mPowerProfile.getNumCpuClusters()).thenReturn(numClusters);
- for (int i = 0; i < numClusters; ++i) {
- when(mPowerProfile.getNumSpeedStepsInCpuCluster(i)).thenReturn(clusterFreqs[i]);
- }
- }
-
- private class VerifiableCallback implements KernelUidCpuFreqTimeReader.Callback {
-
- SparseArray<long[]> mData = new SparseArray<>();
- int count = 0;
-
- public void verify(int uid, long[] cpuFreqTimeMs) {
- long[] array = mData.get(uid);
- assertNotNull(array);
- assertArrayEquals(cpuFreqTimeMs, array);
- count++;
- }
-
- public void clear() {
- mData.clear();
- count = 0;
- }
-
- @Override
- public void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs) {
- long[] array = new long[cpuFreqTimeMs.length];
- System.arraycopy(cpuFreqTimeMs, 0, array, 0, array.length);
- mData.put(uid, array);
- }
-
- public void verifyNoMoreInteractions() {
- assertEquals(mData.size(), count);
- }
- }
-}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index c18445e..bc0e0a4 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -21,6 +21,10 @@
import android.util.SparseIntArray;
import com.android.internal.location.gnssmetrics.GnssMetrics;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
import java.util.ArrayList;
import java.util.Queue;
@@ -43,13 +47,14 @@
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
setExternalStatsSyncLocked(new DummyExternalStatsSync());
- for (int i=0; i< GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) {
- mGpsSignalQualityTimer[i] = new StopwatchTimer(clocks, null, -1000-i, null,
- mOnBatteryTimeBase);
+ for (int i = 0; i < GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) {
+ mGpsSignalQualityTimer[i] = new StopwatchTimer(clocks, null, -1000 - i, null,
+ mOnBatteryTimeBase);
}
// A no-op handler.
- mHandler = new Handler(Looper.getMainLooper()) {};
+ mHandler = new Handler(Looper.getMainLooper()) {
+ };
}
MockBatteryStatsImpl() {
@@ -95,23 +100,26 @@
return this;
}
- public MockBatteryStatsImpl setKernelUidCpuFreqTimeReader(KernelUidCpuFreqTimeReader reader) {
- mKernelUidCpuFreqTimeReader = reader;
+ public MockBatteryStatsImpl setKernelCpuUidFreqTimeReader(KernelCpuUidFreqTimeReader reader) {
+ mCpuUidFreqTimeReader = reader;
return this;
}
- public MockBatteryStatsImpl setKernelUidCpuActiveTimeReader(KernelUidCpuActiveTimeReader reader) {
- mKernelUidCpuActiveTimeReader = reader;
+ public MockBatteryStatsImpl setKernelCpuUidActiveTimeReader(
+ KernelCpuUidActiveTimeReader reader) {
+ mCpuUidActiveTimeReader = reader;
return this;
}
- public MockBatteryStatsImpl setKernelUidCpuClusterTimeReader(KernelUidCpuClusterTimeReader reader) {
- mKernelUidCpuClusterTimeReader = reader;
+ public MockBatteryStatsImpl setKernelCpuUidClusterTimeReader(
+ KernelCpuUidClusterTimeReader reader) {
+ mCpuUidClusterTimeReader = reader;
return this;
}
- public MockBatteryStatsImpl setKernelUidCpuTimeReader(KernelUidCpuTimeReader reader) {
- mKernelUidCpuTimeReader = reader;
+ public MockBatteryStatsImpl setKernelCpuUidUserSysTimeReader(
+ KernelCpuUidUserSysTimeReader reader) {
+ mCpuUidUserSysTimeReader = reader;
return this;
}
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/NotificationVisibilityTest.java b/core/tests/coretests/src/com/android/internal/statusbar/NotificationVisibilityTest.java
new file mode 100644
index 0000000..b740ecc
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/statusbar/NotificationVisibilityTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.internal.statusbar;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class NotificationVisibilityTest {
+
+ @Test
+ public void testNotificationLocation_sameValuesAsMetricsProto() throws Exception {
+ for (NotificationVisibility.NotificationLocation location :
+ NotificationVisibility.NotificationLocation.values()) {
+ Field locationField = MetricsEvent.class.getField(location.name());
+ int metricsValue = locationField.getInt(null);
+ assertThat(metricsValue, is(location.toMetricsEventEnum()));
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java
new file mode 100644
index 0000000..1a81c2c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java
@@ -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.
+ */
+
+package com.android.server.wm.test.filters;
+
+import android.os.Bundle;
+
+import com.android.test.filters.SelectTest;
+
+/**
+ * JUnit test filter that select Window Manager Service related tests from FrameworksCoreTests.
+ *
+ * <p>Use this filter when running FrameworksCoreTests as
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.server.wm.test.filters.CoreTestsFilter \
+ * -e selectTest_verbose true \
+ * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ */
+public final class CoreTestsFilter extends SelectTest {
+
+ private static final String[] SELECTED_CORE_TESTS = {
+ "android.app.servertransaction.", // all tests under the package.
+ "android.view.DisplayCutoutTest",
+ "android.view.InsetsControllerTest",
+ "android.view.InsetsSourceTest",
+ "android.view.InsetsSourceConsumerTest",
+ "android.view.InsetsStateTest",
+ };
+
+ public CoreTestsFilter(Bundle testArgs) {
+ super(addSelectTest(testArgs, SELECTED_CORE_TESTS));
+ }
+}
diff --git a/core/tests/hdmitests/Android.mk b/core/tests/hdmitests/Android.mk
index 2ca31a6..f155feb 100644
--- a/core/tests/hdmitests/Android.mk
+++ b/core/tests/hdmitests/Android.mk
@@ -20,7 +20,7 @@
# Include all test java files
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules frameworks-base-testutils
+LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules frameworks-base-testutils truth-prebuilt
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := HdmiCecTests
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java
new file mode 100644
index 0000000..fdc6b84
--- /dev/null
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.hdmi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+/**
+ * Tests for {@link HdmiUtils}.
+ */
+@RunWith(JUnit4.class)
+@SmallTest
+public class HdmiUtilsTest {
+ @Test
+ public void testInvalidAddress() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0, -1))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN);
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0xFFFF, 0xFFFF))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN);
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0xFFFFF, 0))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN);
+ }
+
+ @Test
+ public void testSameAddress() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1000, 0x1000))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_SAME);
+ }
+
+ @Test
+ public void testDirectlyAbove() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1000, 0x1200))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE);
+ }
+
+ @Test
+ public void testDirectlyAbove_rootDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x0000, 0x2000))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE);
+ }
+
+ @Test
+ public void testDirectlyAbove_leafDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1240, 0x1245))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE);
+ }
+
+ @Test
+ public void testAbove() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1000, 0x1210))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_ABOVE);
+ }
+
+ @Test
+ public void testAbove_rootDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x0000, 0x1200))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_ABOVE);
+ }
+
+ @Test
+ public void testDirectlyBelow() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x2250, 0x2200))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW);
+ }
+
+ @Test
+ public void testDirectlyBelow_rootDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x5000, 0x0000))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW);
+ }
+
+ @Test
+ public void testDirectlyBelow_leafDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x3249, 0x3240))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW);
+ }
+
+ @Test
+ public void testBelow() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x5143, 0x5100))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_BELOW);
+ }
+
+ @Test
+ public void testBelow_rootDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x3420, 0x0000))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_BELOW);
+ }
+
+ @Test
+ public void testSibling() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x4000, 0x5000))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_SIBLING);
+ }
+
+ @Test
+ public void testSibling_leafDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x798A, 0x798F))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_SIBLING);
+ }
+
+ @Test
+ public void testDifferentBranch() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x798A, 0x7970))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH);
+ }
+
+ @Test
+ public void isValidPysicalAddress_true() {
+ assertThat(HdmiUtils.isValidPhysicalAddress(0)).isTrue();
+ assertThat(HdmiUtils.isValidPhysicalAddress(0xFFFE)).isTrue();
+ assertThat(HdmiUtils.isValidPhysicalAddress(0x1200)).isTrue();
+ }
+
+ @Test
+ public void isValidPysicalAddress_outOfRange() {
+ assertThat(HdmiUtils.isValidPhysicalAddress(-1)).isFalse();
+ assertThat(HdmiUtils.isValidPhysicalAddress(0xFFFF)).isFalse();
+ assertThat(HdmiUtils.isValidPhysicalAddress(0x10000)).isFalse();
+ }
+
+ @Test
+ public void isValidPysicalAddress_nonTrailingZeros() {
+ assertThat(HdmiUtils.isValidPhysicalAddress(0x0001)).isFalse();
+ assertThat(HdmiUtils.isValidPhysicalAddress(0x0213)).isFalse();
+ }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index 03cf3eb..9913531 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -67,6 +68,7 @@
// TODO(b/63758238): stop spying the class under test
mLockPatternUtils = spy(new LockPatternUtils(context));
when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
+ doReturn(true).when(mLockPatternUtils).hasSecureLockScreen();
final UserInfo userInfo = Mockito.mock(UserInfo.class);
when(userInfo.isDemo()).thenReturn(isDemoUser);
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 597d14a..904c3fb 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -180,6 +180,7 @@
<permission name="android.permission.WRITE_APN_SETTINGS"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
<permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/>
+ <permission name="android.permission.READ_PRECISE_PHONE_STATE"/>
<permission name="com.android.voicemail.permission.READ_VOICEMAIL"/>
<permission name="com.android.voicemail.permission.WRITE_VOICEMAIL"/>
</privapp-permissions>
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 30f0bfa..bfbdbc5 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -21,7 +21,6 @@
import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.Size;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.annotation.WorkerThread;
@@ -748,22 +747,10 @@
throw new IllegalArgumentException("usage flags must contain USAGE_GPU_SAMPLED_IMAGE.");
}
int format = hardwareBuffer.getFormat();
- ColorSpace.Rgb rgb = null;
- if (colorSpace != null) {
- if (!(colorSpace instanceof ColorSpace.Rgb)) {
- throw new IllegalArgumentException("colorSpace must be an RGB color space");
- }
- rgb = (ColorSpace.Rgb) colorSpace;
- } else {
- rgb = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
+ if (colorSpace == null) {
+ colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
}
- ColorSpace.Rgb.TransferParameters parameters = rgb.getTransferParameters();
- if (parameters == null) {
- throw new IllegalArgumentException("colorSpace must use an ICC "
- + "parametric transfer function");
- }
- ColorSpace.Rgb d50 = (ColorSpace.Rgb) ColorSpace.adapt(rgb, ColorSpace.ILLUMINANT_D50);
- return nativeWrapHardwareBufferBitmap(hardwareBuffer, d50.getTransform(), parameters);
+ return nativeWrapHardwareBufferBitmap(hardwareBuffer, colorSpace.getNativeInstance());
}
/**
@@ -1103,26 +1090,18 @@
throw new IllegalArgumentException("can't create bitmap without a color space");
}
- Bitmap bm;
- // nullptr color spaces have a particular meaning in native and are interpreted as sRGB
- // (we also avoid the unnecessary extra work of the else branch)
- if (config != Config.ARGB_8888 || colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)) {
- bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true, null, null);
- } else {
- if (!(colorSpace instanceof ColorSpace.Rgb)) {
- throw new IllegalArgumentException("colorSpace must be an RGB color space");
+ if (config != Config.ARGB_8888) {
+ if (config == Config.RGBA_F16) {
+ // FIXME: This should be LINEAR_EXTENDED_SRGB, but that would fail a CTS test. See
+ // b/120960866. SRGB matches the old (incorrect) behavior.
+ //colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
+ colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+ } else {
+ colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
}
- ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace;
- ColorSpace.Rgb.TransferParameters parameters = rgb.getTransferParameters();
- if (parameters == null) {
- throw new IllegalArgumentException("colorSpace must use an ICC "
- + "parametric transfer function");
- }
-
- ColorSpace.Rgb d50 = (ColorSpace.Rgb) ColorSpace.adapt(rgb, ColorSpace.ILLUMINANT_D50);
- bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true,
- d50.getTransform(), parameters);
}
+ Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true,
+ colorSpace.getNativeInstance());
if (display != null) {
bm.mDensity = display.densityDpi;
@@ -1200,8 +1179,9 @@
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width and height must be > 0");
}
+ ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB);
Bitmap bm = nativeCreate(colors, offset, stride, width, height,
- config.nativeInt, false, null, null);
+ config.nativeInt, false, sRGB.getNativeInstance());
if (display != null) {
bm.mDensity = display.densityDpi;
}
@@ -1798,11 +1778,7 @@
}
ColorSpace cs = Color.colorSpace(c);
- float r = Color.red(c);
- float g = Color.green(c);
- float b = Color.blue(c);
- float a = Color.alpha(c);
- nativeErase(mNativePtr, cs, r, g, b, a);
+ nativeErase(mNativePtr, cs.getNativeInstance(), c);
}
/**
@@ -2133,8 +2109,7 @@
private static native Bitmap nativeCreate(int[] colors, int offset,
int stride, int width, int height,
int nativeConfig, boolean mutable,
- @Nullable @Size(9) float[] xyzD50,
- @Nullable ColorSpace.Rgb.TransferParameters p);
+ long nativeColorSpace);
private static native Bitmap nativeCopy(long nativeSrcBitmap, int nativeConfig,
boolean isMutable);
private static native Bitmap nativeCopyAshmem(long nativeSrcBitmap);
@@ -2149,8 +2124,7 @@
int quality, OutputStream stream,
byte[] tempStorage);
private static native void nativeErase(long nativeBitmap, int color);
- private static native void nativeErase(long nativeBitmap, ColorSpace cs,
- float r, float g, float b, float a);
+ private static native void nativeErase(long nativeBitmap, long colorSpacePtr, long color);
private static native int nativeRowBytes(long nativeBitmap);
private static native int nativeConfig(long nativeBitmap);
@@ -2194,8 +2168,7 @@
private static native Bitmap nativeCopyPreserveInternalConfig(long nativeBitmap);
private static native Bitmap nativeCreateHardwareBitmap(GraphicBuffer buffer);
private static native Bitmap nativeWrapHardwareBufferBitmap(HardwareBuffer buffer,
- @Size(9) float[] xyzD50,
- ColorSpace.Rgb.TransferParameters p);
+ long nativeColorSpace);
private static native GraphicBuffer nativeCreateGraphicBufferHandle(long nativeBitmap);
private static native boolean nativeGetColorSpace(long nativePtr, float[] xyz, float[] params);
private static native boolean nativeIsSRGB(long nativePtr);
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 022fbdc..7aff041 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -460,6 +460,21 @@
}
}
}
+
+ /**
+ * Helper for passing SkColorSpace pointer to native.
+ *
+ * @throws IllegalArgumentException if the ColorSpace is not Rgb or does
+ * not have TransferParameters.
+ */
+ static long nativeColorSpace(Options opts) {
+ if (opts == null || opts.inPreferredColorSpace == null) {
+ return 0;
+ }
+
+ return opts.inPreferredColorSpace.getNativeInstance();
+ }
+
}
/**
@@ -633,7 +648,8 @@
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
try {
- bm = nativeDecodeByteArray(data, offset, length, opts);
+ bm = nativeDecodeByteArray(data, offset, length, opts,
+ Options.nativeColorSpace(opts));
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
@@ -728,7 +744,7 @@
try {
if (is instanceof AssetManager.AssetInputStream) {
final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
- bm = nativeDecodeAsset(asset, outPadding, opts);
+ bm = nativeDecodeAsset(asset, outPadding, opts, Options.nativeColorSpace(opts));
} else {
bm = decodeStreamInternal(is, outPadding, opts);
}
@@ -755,7 +771,8 @@
byte [] tempStorage = null;
if (opts != null) tempStorage = opts.inTempStorage;
if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
- return nativeDecodeStream(is, tempStorage, outPadding, opts);
+ return nativeDecodeStream(is, tempStorage, outPadding, opts,
+ Options.nativeColorSpace(opts));
}
/**
@@ -798,7 +815,8 @@
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeFileDescriptor");
try {
if (nativeIsSeekable(fd)) {
- bm = nativeDecodeFileDescriptor(fd, outPadding, opts);
+ bm = nativeDecodeFileDescriptor(fd, outPadding, opts,
+ Options.nativeColorSpace(opts));
} else {
FileInputStream fis = new FileInputStream(fd);
try {
@@ -835,14 +853,15 @@
@UnsupportedAppUsage
private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
- Rect padding, Options opts);
+ Rect padding, Options opts, long colorSpaceHandle);
@UnsupportedAppUsage
private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
- Rect padding, Options opts);
+ Rect padding, Options opts, long colorSpaceHandle);
@UnsupportedAppUsage
- private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);
+ private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts,
+ long colorSpaceHandle);
@UnsupportedAppUsage
private static native Bitmap nativeDecodeByteArray(byte[] data, int offset,
- int length, Options opts);
+ int length, Options opts, long colorSpaceHandle);
private static native boolean nativeIsSeekable(FileDescriptor fd);
}
diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java
index 43282d3..1410423 100644
--- a/graphics/java/android/graphics/BitmapRegionDecoder.java
+++ b/graphics/java/android/graphics/BitmapRegionDecoder.java
@@ -195,7 +195,8 @@
|| rect.top >= getHeight())
throw new IllegalArgumentException("rectangle is outside the image");
return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
- rect.right - rect.left, rect.bottom - rect.top, options);
+ rect.right - rect.left, rect.bottom - rect.top, options,
+ BitmapFactory.Options.nativeColorSpace(options));
}
}
@@ -265,7 +266,7 @@
private static native Bitmap nativeDecodeRegion(long lbm,
int start_x, int start_y, int width, int height,
- BitmapFactory.Options options);
+ BitmapFactory.Options options, long colorSpaceHandle);
private static native int nativeGetWidth(long lbm);
private static native int nativeGetHeight(long lbm);
private static native void nativeClean(long lbm);
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 9fa70a5..4755d45 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -25,6 +25,8 @@
import android.annotation.SuppressAutoDoc;
import android.util.Pair;
+import libcore.util.NativeAllocationRegistry;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -199,6 +201,9 @@
private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
private static final float[] ILLUMINANT_D50_XYZ = { 0.964212f, 1.0f, 0.825188f };
+ private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS =
+ new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4);
+
// See static initialization block next to #get(Named)
private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
@@ -1341,6 +1346,26 @@
}
/**
+ * Helper method for creating native SkColorSpace.
+ *
+ * This essentially calls adapt on a ColorSpace that has not been fully
+ * created. It also does not fully create the adapted ColorSpace, but
+ * just returns the transform.
+ */
+ @NonNull @Size(9)
+ private static float[] adaptToIlluminantD50(
+ @NonNull @Size(2) float[] origWhitePoint,
+ @NonNull @Size(9) float[] origTransform) {
+ float[] desired = ILLUMINANT_D50;
+ if (compare(origWhitePoint, desired)) return origTransform;
+
+ float[] xyz = xyYToXyz(desired);
+ float[] adaptationTransform = chromaticAdaptation(Adaptation.BRADFORD.mTransform,
+ xyYToXyz(origWhitePoint), xyz);
+ return mul3x3(adaptationTransform, origTransform);
+ }
+
+ /**
* <p>Returns an instance of {@link ColorSpace} whose ID matches the
* specified ID.</p>
*
@@ -1431,7 +1456,7 @@
"sRGB IEC61966-2.1",
SRGB_PRIMARIES,
ILLUMINANT_D65,
- new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
+ SRGB_TRANSFER_PARAMETERS,
Named.SRGB.ordinal()
);
sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb(
@@ -1446,9 +1471,11 @@
"scRGB-nl IEC 61966-2-2:2003",
SRGB_PRIMARIES,
ILLUMINANT_D65,
+ null,
x -> absRcpResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
x -> absResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
-0.799f, 2.399f,
+ null, // FIXME: Use SRGB_TRANSFER_PARAMETERS
Named.EXTENDED_SRGB.ordinal()
);
sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
@@ -1485,7 +1512,7 @@
"Display P3",
new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
ILLUMINANT_D65,
- new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
+ SRGB_TRANSFER_PARAMETERS,
Named.DISPLAY_P3.ordinal()
);
sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb(
@@ -1967,6 +1994,15 @@
}
/**
+ * Retrieve the native SkColorSpace object for passing to native.
+ *
+ * Only valid on ColorSpace.Rgb.
+ */
+ long getNativeInstance() {
+ throw new IllegalArgumentException("colorSpace must be an RGB color space");
+ }
+
+ /**
* {@usesMathJax}
*
* <p>An RGB color space is an additive color space using the
@@ -2269,7 +2305,22 @@
private final boolean mIsWideGamut;
private final boolean mIsSrgb;
- @Nullable private TransferParameters mTransferParameters;
+ @Nullable private final TransferParameters mTransferParameters;
+ private final long mNativePtr;
+
+ @Override
+ long getNativeInstance() {
+ if (mNativePtr == 0) {
+ // If this object has TransferParameters, it must have a native object.
+ throw new IllegalArgumentException("ColorSpace must use an ICC "
+ + "parametric transfer function! used " + this);
+ }
+ return mNativePtr;
+ }
+
+ private static native long nativeGetNativeFinalizer();
+ private static native long nativeCreate(float a, float b, float c, float d,
+ float e, float f, float g, float[] xyz);
/**
* <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
@@ -2298,8 +2349,8 @@
@NonNull @Size(9) float[] toXYZ,
@NonNull DoubleUnaryOperator oetf,
@NonNull DoubleUnaryOperator eotf) {
- this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ),
- oetf, eotf, 0.0f, 1.0f, MIN_ID);
+ this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), null,
+ oetf, eotf, 0.0f, 1.0f, null, MIN_ID);
}
/**
@@ -2349,7 +2400,7 @@
@NonNull DoubleUnaryOperator eotf,
float min,
float max) {
- this(name, primaries, whitePoint, oetf, eotf, min, max, MIN_ID);
+ this(name, primaries, whitePoint, null, oetf, eotf, min, max, null, MIN_ID);
}
/**
@@ -2459,7 +2510,7 @@
@NonNull @Size(min = 2, max = 3) float[] whitePoint,
@NonNull TransferParameters function,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
- this(name, primaries, whitePoint,
+ this(name, primaries, whitePoint, null,
function.e == 0.0 && function.f == 0.0 ?
x -> rcpResponse(x, function.a, function.b,
function.c, function.d, function.g) :
@@ -2470,8 +2521,7 @@
function.c, function.d, function.g) :
x -> response(x, function.a, function.b, function.c,
function.d, function.e, function.f, function.g),
- 0.0f, 1.0f, id);
- mTransferParameters = function;
+ 0.0f, 1.0f, function, id);
}
/**
@@ -2586,13 +2636,12 @@
float min,
float max,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
- this(name, primaries, whitePoint,
+ this(name, primaries, whitePoint, null,
gamma == 1.0 ? DoubleUnaryOperator.identity() :
x -> Math.pow(x < 0.0 ? 0.0 : x, 1 / gamma),
gamma == 1.0 ? DoubleUnaryOperator.identity() :
x -> Math.pow(x < 0.0 ? 0.0 : x, gamma),
- min, max, id);
- mTransferParameters = new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma);
+ min, max, new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma), id);
}
/**
@@ -2615,10 +2664,13 @@
* @param name Name of the color space, cannot be null, its length must be >= 1
* @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
* @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
+ * @param transform Computed transform matrix that converts from RGB to XYZ, or
+ * {@code null} to compute it from {@code primaries} and {@code whitePoint}.
* @param oetf Opto-electronic transfer function, cannot be null
* @param eotf Electro-optical transfer function, cannot be null
* @param min The minimum valid value in this color space's RGB range
* @param max The maximum valid value in this color space's RGB range
+ * @param transferParameters Parameters for the transfer functions
* @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
*
* @throws IllegalArgumentException If any of the following conditions is met:
@@ -2637,10 +2689,12 @@
@NonNull @Size(min = 1) String name,
@NonNull @Size(min = 6, max = 9) float[] primaries,
@NonNull @Size(min = 2, max = 3) float[] whitePoint,
+ @Nullable @Size(9) float[] transform,
@NonNull DoubleUnaryOperator oetf,
@NonNull DoubleUnaryOperator eotf,
float min,
float max,
+ @Nullable TransferParameters transferParameters,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
super(name, Model.RGB, id);
@@ -2668,7 +2722,15 @@
mWhitePoint = xyWhitePoint(whitePoint);
mPrimaries = xyPrimaries(primaries);
- mTransform = computeXYZMatrix(mPrimaries, mWhitePoint);
+ if (transform == null) {
+ mTransform = computeXYZMatrix(mPrimaries, mWhitePoint);
+ } else {
+ if (transform.length != 9) {
+ throw new IllegalArgumentException("Transform must have 9 entries! Has "
+ + transform.length);
+ }
+ mTransform = transform;
+ }
mInverseTransform = inverse3x3(mTransform);
mOetf = oetf;
@@ -2681,10 +2743,39 @@
mClampedOetf = oetf.andThen(clamp);
mClampedEotf = clamp.andThen(eotf);
+ mTransferParameters = transferParameters;
+
// A color space is wide-gamut if its area is >90% of NTSC 1953 and
// if it entirely contains the Color space definition in xyY
mIsWideGamut = isWideGamut(mPrimaries, min, max);
mIsSrgb = isSrgb(mPrimaries, mWhitePoint, oetf, eotf, min, max, id);
+
+ if (mTransferParameters != null) {
+ if (mWhitePoint == null || mTransform == null) {
+ throw new IllegalStateException(
+ "ColorSpace (" + this + ") cannot create native object! mWhitePoint: "
+ + mWhitePoint + " mTransform: " + mTransform);
+ }
+
+ // This mimics the old code that was in native.
+ float[] nativeTransform = adaptToIlluminantD50(mWhitePoint, mTransform);
+ mNativePtr = nativeCreate((float) mTransferParameters.a,
+ (float) mTransferParameters.b,
+ (float) mTransferParameters.c,
+ (float) mTransferParameters.d,
+ (float) mTransferParameters.e,
+ (float) mTransferParameters.f,
+ (float) mTransferParameters.g,
+ nativeTransform);
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePtr);
+ } else {
+ mNativePtr = 0;
+ }
+ }
+
+ private static class NoImagePreloadHolder {
+ public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ ColorSpace.Rgb.class.getClassLoader(), nativeGetNativeFinalizer(), 0);
}
/**
@@ -2695,27 +2786,9 @@
private Rgb(Rgb colorSpace,
@NonNull @Size(9) float[] transform,
@NonNull @Size(min = 2, max = 3) float[] whitePoint) {
- super(colorSpace.getName(), Model.RGB, -1);
-
- mWhitePoint = xyWhitePoint(whitePoint);
- mPrimaries = colorSpace.mPrimaries;
-
- mTransform = transform;
- mInverseTransform = inverse3x3(transform);
-
- mMin = colorSpace.mMin;
- mMax = colorSpace.mMax;
-
- mOetf = colorSpace.mOetf;
- mEotf = colorSpace.mEotf;
-
- mClampedOetf = colorSpace.mClampedOetf;
- mClampedEotf = colorSpace.mClampedEotf;
-
- mIsWideGamut = colorSpace.mIsWideGamut;
- mIsSrgb = colorSpace.mIsSrgb;
-
- mTransferParameters = colorSpace.mTransferParameters;
+ this(colorSpace.getName(), colorSpace.mPrimaries, whitePoint, transform,
+ colorSpace.mOetf, colorSpace.mEotf, colorSpace.mMin, colorSpace.mMax,
+ colorSpace.mTransferParameters, MIN_ID);
}
/**
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index e402055..c4ddd50 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -20,6 +20,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.app.Activity;
import android.app.ActivityManager;
import android.os.IBinder;
@@ -667,6 +668,17 @@
nSetContentDrawBounds(mNativeProxy, left, top, right, bottom);
}
+ /** @hide */
+ public void setPictureCaptureCallback(@Nullable PictureCapturedCallback callback) {
+ nSetPictureCaptureCallback(mNativeProxy, callback);
+ }
+
+ /** called by native */
+ static void invokePictureCapturedCallback(long picturePtr, PictureCapturedCallback callback) {
+ Picture picture = new Picture(picturePtr);
+ callback.onPictureCaptured(picture);
+ }
+
/**
* Interface used to receive callbacks when a frame is being drawn.
*
@@ -695,6 +707,17 @@
void onFrameComplete(long frameNr);
}
+ /**
+ * Interface for listening to picture captures
+ * @hide
+ */
+ @TestApi
+ public interface PictureCapturedCallback {
+ /** @hide */
+ @TestApi
+ void onPictureCaptured(Picture picture);
+ }
+
private static void validateAlpha(float alpha, String argumentName) {
if (!(alpha >= 0.0f && alpha <= 1.0f)) {
throw new IllegalArgumentException(argumentName + " must be a valid alpha, "
@@ -998,6 +1021,9 @@
private static native void nSetContentDrawBounds(long nativeProxy, int left,
int top, int right, int bottom);
+ private static native void nSetPictureCaptureCallback(long nativeProxy,
+ PictureCapturedCallback callback);
+
private static native void nSetFrameCallback(long nativeProxy, FrameDrawingCallback callback);
private static native void nSetFrameCompleteCallback(long nativeProxy,
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index e3b165c..466a5fc 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -1628,17 +1628,6 @@
if (mPostProcessor != null && mUnpremultipliedRequired) {
throw new IllegalStateException("Cannot draw to unpremultiplied pixels!");
}
-
- if (mDesiredColorSpace != null) {
- if (!(mDesiredColorSpace instanceof ColorSpace.Rgb)) {
- throw new IllegalArgumentException("The target color space must use the "
- + "RGB color model - provided: " + mDesiredColorSpace);
- }
- if (((ColorSpace.Rgb) mDesiredColorSpace).getTransferParameters() == null) {
- throw new IllegalArgumentException("The target color space must use an "
- + "ICC parametric transfer function - provided: " + mDesiredColorSpace);
- }
- }
}
private static void checkSubset(int width, int height, Rect r) {
@@ -1655,10 +1644,12 @@
@NonNull
private Bitmap decodeBitmapInternal() throws IOException {
checkState();
+ long colorSpacePtr = mDesiredColorSpace == null ? 0 :
+ mDesiredColorSpace.getNativeInstance();
return nDecodeBitmap(mNativePtr, this, mPostProcessor != null,
mDesiredWidth, mDesiredHeight, mCropRect,
mMutable, mAllocator, mUnpremultipliedRequired,
- mConserveMemory, mDecodeAsAlphaMask, mDesiredColorSpace);
+ mConserveMemory, mDecodeAsAlphaMask, colorSpacePtr);
}
private void callHeaderDecoded(@Nullable OnHeaderDecodedListener listener,
@@ -1946,7 +1937,7 @@
@Nullable Rect cropRect, boolean mutable,
int allocator, boolean unpremulRequired,
boolean conserveMemory, boolean decodeAsAlphaMask,
- @Nullable ColorSpace desiredColorSpace)
+ long desiredColorSpace)
throws IOException;
private static native Size nGetSampledSize(long nativePtr,
int sampleSize);
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 54e1abc..7eee6f4 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1017,7 +1017,7 @@
float b = Color.blue(color);
float a = Color.alpha(color);
- nSetColor(mNativePaint, cs, r, g, b, a);
+ nSetColor(mNativePaint, cs.getNativeInstance(), r, g, b, a);
mColor = color;
}
@@ -1455,7 +1455,7 @@
float g = Color.green(shadowColor);
float b = Color.blue(shadowColor);
float a = Color.alpha(shadowColor);
- nSetShadowLayer(mNativePaint, radius, dx, dy, cs, r, g, b, a);
+ nSetShadowLayer(mNativePaint, radius, dx, dy, cs.getNativeInstance(), r, g, b, a);
mShadowLayerRadius = radius;
mShadowLayerDx = dx;
@@ -3003,11 +3003,6 @@
int contextStart, int contextEnd, boolean isRtl, int offset);
private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end,
int contextStart, int contextEnd, boolean isRtl, float advance);
- private static native void nSetColor(long paintPtr, ColorSpace cs,
- float r, float g, float b, float a);
- private static native void nSetShadowLayer(long paintPtr,
- float radius, float dx, float dy, ColorSpace cs,
- float r, float g, float b, float a);
// ---------------- @FastNative ------------------------
@@ -3063,7 +3058,8 @@
int mMinikinLocaleListId);
@CriticalNative
private static native void nSetShadowLayer(long paintPtr,
- float radius, float dx, float dy, @ColorInt int color);
+ float radius, float dx, float dy, long colorSpaceHandle,
+ float r, float g, float b, float a);
@CriticalNative
private static native boolean nHasShadowLayer(long paintPtr);
@CriticalNative
@@ -3111,6 +3107,9 @@
@CriticalNative
private static native void nSetFilterBitmap(long paintPtr, boolean filter);
@CriticalNative
+ private static native void nSetColor(long paintPtr, long colorSpaceHandle,
+ float r, float g, float b, float a);
+ @CriticalNative
private static native void nSetStrikeThruText(long paintPtr, boolean strikeThruText);
@CriticalNative
private static native boolean nIsElegantTextHeight(long paintPtr);
diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java
index f6d801b..8d12cbf 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.annotation.NonNull;
import android.annotation.UnsupportedAppUsage;
import java.io.InputStream;
@@ -34,7 +35,8 @@
*/
public class Picture {
private PictureCanvas mRecordingCanvas;
- @UnsupportedAppUsage
+ // TODO: Figure out if this was a false-positive
+ @UnsupportedAppUsage(maxTargetSdk = 28)
private long mNativePicture;
private boolean mRequiresHwAcceleration;
@@ -56,23 +58,43 @@
this(nativeConstructor(src != null ? src.mNativePicture : 0));
}
- private Picture(long nativePicture) {
+ /** @hide */
+ public Picture(long nativePicture) {
if (nativePicture == 0) {
- throw new RuntimeException();
+ throw new IllegalArgumentException();
}
mNativePicture = nativePicture;
}
+ /**
+ * Immediately releases the backing data of the Picture. This object will no longer
+ * be usable after calling this, and any further calls on the Picture will throw an
+ * IllegalStateException.
+ * // TODO: Support?
+ * @hide
+ */
+ public void close() {
+ if (mNativePicture != 0) {
+ nativeDestructor(mNativePicture);
+ mNativePicture = 0;
+ }
+ }
+
@Override
protected void finalize() throws Throwable {
try {
- nativeDestructor(mNativePicture);
- mNativePicture = 0;
+ close();
} finally {
super.finalize();
}
}
+ private void verifyValid() {
+ if (mNativePicture == 0) {
+ throw new IllegalStateException("Picture is destroyed");
+ }
+ }
+
/**
* To record a picture, call beginRecording() and then draw into the Canvas
* that is returned. Nothing we appear on screen, but all of the draw
@@ -81,7 +103,9 @@
* that was returned must no longer be used, and nothing should be drawn
* into it.
*/
+ @NonNull
public Canvas beginRecording(int width, int height) {
+ verifyValid();
if (mRecordingCanvas != null) {
throw new IllegalStateException("Picture already recording, must call #endRecording()");
}
@@ -98,6 +122,7 @@
* or {@link Canvas#drawPicture(Picture)} is called.
*/
public void endRecording() {
+ verifyValid();
if (mRecordingCanvas != null) {
mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap;
mRecordingCanvas = null;
@@ -110,7 +135,8 @@
* does not reflect (per se) the content of the picture.
*/
public int getWidth() {
- return nativeGetWidth(mNativePicture);
+ verifyValid();
+ return nativeGetWidth(mNativePicture);
}
/**
@@ -118,7 +144,8 @@
* does not reflect (per se) the content of the picture.
*/
public int getHeight() {
- return nativeGetHeight(mNativePicture);
+ verifyValid();
+ return nativeGetHeight(mNativePicture);
}
/**
@@ -133,6 +160,7 @@
* false otherwise.
*/
public boolean requiresHardwareAcceleration() {
+ verifyValid();
return mRequiresHwAcceleration;
}
@@ -149,7 +177,8 @@
*
* @param canvas The picture is drawn to this canvas
*/
- public void draw(Canvas canvas) {
+ public void draw(@NonNull Canvas canvas) {
+ verifyValid();
if (mRecordingCanvas != null) {
endRecording();
}
@@ -172,7 +201,7 @@
* raw or compressed pixels.
*/
@Deprecated
- public static Picture createFromStream(InputStream stream) {
+ public static Picture createFromStream(@NonNull InputStream stream) {
return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE]));
}
@@ -188,10 +217,11 @@
* Bitmap from which you can persist it as raw or compressed pixels.
*/
@Deprecated
- public void writeToStream(OutputStream stream) {
+ public void writeToStream(@NonNull OutputStream stream) {
+ verifyValid();
// do explicit check before calling the native method
if (stream == null) {
- throw new NullPointerException();
+ throw new IllegalArgumentException("stream cannot be null");
}
if (!nativeWriteToStream(mNativePicture, stream, new byte[WORKING_STREAM_STORAGE])) {
throw new RuntimeException();
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 3b1d44b..09b18b7 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -184,7 +184,7 @@
*
* @param name The name of the RenderNode, used for debugging purpose. May be null.
*/
- public RenderNode(String name) {
+ public RenderNode(@Nullable String name) {
this(name, null);
}
diff --git a/graphics/proto/Android.bp b/graphics/proto/Android.bp
new file mode 100644
index 0000000..1d06348
--- /dev/null
+++ b/graphics/proto/Android.bp
@@ -0,0 +1,11 @@
+java_library_static {
+ name: "game-driver-protos",
+ host_supported: true,
+ proto: {
+ type: "lite",
+ },
+ srcs: ["game_driver.proto"],
+ no_framework_libs: true,
+ jarjar_rules: "jarjar-rules.txt",
+ sdk_version: "28",
+}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/graphics/proto/game_driver.proto
similarity index 61%
copy from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
copy to graphics/proto/game_driver.proto
index 62a8c48..fd7ffcc 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/graphics/proto/game_driver.proto
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -14,10 +14,18 @@
* limitations under the License.
*/
-package com.android.testing.alarmservice;
+syntax = "proto2";
-interface Alarm {
- int prepare();
- int setAlarmAndWait(long timeoutMills);
- int done();
-}
\ No newline at end of file
+package android.gamedriver;
+
+option java_package = "android.gamedriver";
+option java_outer_classname = "GameDriverProto";
+
+message Blacklist {
+ optional int64 version_code = 1;
+ repeated string package_names = 2;
+}
+
+message Blacklists {
+ repeated Blacklist blacklists = 1;
+}
diff --git a/graphics/proto/jarjar-rules.txt b/graphics/proto/jarjar-rules.txt
new file mode 100644
index 0000000..4e40637
--- /dev/null
+++ b/graphics/proto/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.protobuf.** com.android.framework.protobuf.@1
diff --git a/jarjar_rules_hidl.txt b/jarjar_rules_hidl.txt
new file mode 100644
index 0000000..4b2331d
--- /dev/null
+++ b/jarjar_rules_hidl.txt
@@ -0,0 +1 @@
+rule android.hidl.** android.internal.hidl.@1
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index dd62bbb..7265692 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -551,6 +551,19 @@
SkPaint paint = inPaint;
paint.setAlpha(mProperties.getRootAlpha() * 255);
+ if (canvas->getGrContext() == nullptr) {
+ // Recording to picture, don't use the SkSurface which won't work off of renderthread.
+ Bitmap& bitmap = getBitmapUpdateIfDirty();
+ SkBitmap skiaBitmap;
+ bitmap.getSkBitmap(&skiaBitmap);
+
+ int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
+ int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
+ canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
+ &paint, SkCanvas::kFast_SrcRectConstraint);
+ return;
+ }
+
SkRect src;
sk_sp<SkSurface> vdSurface = mCache.getSurface(&src);
if (vdSurface) {
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 240efb4..60c8057 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -74,7 +74,13 @@
void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
if (canvas->getGrContext() == nullptr) {
- SkDEBUGF(("Attempting to draw GLFunctor into an unsupported surface"));
+ // We're dumping a picture, render a light-blue rectangle instead
+ // TODO: Draw the WebView text on top? Seemingly complicated as SkPaint doesn't
+ // seem to have a default typeface that works. We only ever use drawGlyphs, which
+ // requires going through minikin & hwui's canvas which we don't have here.
+ SkPaint paint;
+ paint.setColor(0xFF81D4FA);
+ canvas->drawRect(mBounds, paint);
return;
}
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index df82243..47c9094 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -111,7 +111,7 @@
const Rect& layerDamage = layers.entries()[i].damage;
- SkCanvas* layerCanvas = tryCapture(layerNode->getLayerSurface());
+ SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
int saveCount = layerCanvas->save();
SkASSERT(saveCount == 1);
@@ -139,8 +139,6 @@
layerCanvas->restoreToCount(saveCount);
mLightCenter = savedLightCenter;
- endCapture(layerNode->getLayerSurface());
-
// cache the current context so that we can defer flushing it until
// either all the layers have been rendered or the context changes
GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext();
@@ -244,6 +242,7 @@
}
virtual void onProcess(const sp<Task<bool>>& task) override {
+ ATRACE_NAME("SavePictureTask");
SavePictureTask* t = static_cast<SavePictureTask*>(task.get());
if (0 == access(t->filename.c_str(), F_OK)) {
@@ -265,46 +264,56 @@
SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) {
if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
- bool recordingPicture = mCaptureSequence > 0;
char prop[PROPERTY_VALUE_MAX] = {'\0'};
- if (!recordingPicture) {
+ if (mCaptureSequence <= 0) {
property_get(PROPERTY_CAPTURE_SKP_FILENAME, prop, "0");
- recordingPicture = prop[0] != '0' &&
- mCapturedFile != prop; // ensure we capture only once per filename
- if (recordingPicture) {
+ if (prop[0] != '0' && mCapturedFile != prop) {
mCapturedFile = prop;
mCaptureSequence = property_get_int32(PROPERTY_CAPTURE_SKP_FRAMES, 1);
}
}
- if (recordingPicture) {
+ if (mCaptureSequence > 0 || mPictureCapturedCallback) {
mRecorder.reset(new SkPictureRecorder());
- return mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
- SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
+ SkCanvas* pictureCanvas = mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
+ SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
+ mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
+ mNwayCanvas->addCanvas(surface->getCanvas());
+ mNwayCanvas->addCanvas(pictureCanvas);
+ return mNwayCanvas.get();
}
}
return surface->getCanvas();
}
void SkiaPipeline::endCapture(SkSurface* surface) {
+ mNwayCanvas.reset();
if (CC_UNLIKELY(mRecorder.get())) {
+ ATRACE_CALL();
sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture();
- surface->getCanvas()->drawPicture(picture);
if (picture->approximateOpCount() > 0) {
- auto data = picture->serialize();
+ if (mCaptureSequence > 0) {
+ ATRACE_BEGIN("picture->serialize");
+ auto data = picture->serialize();
+ ATRACE_END();
- // offload saving to file in a different thread
- if (!mSavePictureProcessor.get()) {
- TaskManager* taskManager = getTaskManager();
- mSavePictureProcessor = new SavePictureProcessor(
- taskManager->canRunTasks() ? taskManager : nullptr);
+ // offload saving to file in a different thread
+ if (!mSavePictureProcessor.get()) {
+ TaskManager* taskManager = getTaskManager();
+ mSavePictureProcessor = new SavePictureProcessor(
+ taskManager->canRunTasks() ? taskManager : nullptr);
+ }
+ if (1 == mCaptureSequence) {
+ mSavePictureProcessor->savePicture(data, mCapturedFile);
+ } else {
+ mSavePictureProcessor->savePicture(
+ data,
+ mCapturedFile + "_" + std::to_string(mCaptureSequence));
+ }
+ mCaptureSequence--;
}
- if (1 == mCaptureSequence) {
- mSavePictureProcessor->savePicture(data, mCapturedFile);
- } else {
- mSavePictureProcessor->savePicture(
- data, mCapturedFile + "_" + std::to_string(mCaptureSequence));
+ if (mPictureCapturedCallback) {
+ std::invoke(mPictureCapturedCallback, std::move(picture));
}
- mCaptureSequence--;
}
mRecorder.reset();
}
@@ -314,6 +323,11 @@
const std::vector<sp<RenderNode>>& nodes, bool opaque,
const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
const SkMatrix& preTransform) {
+ bool previousSkpEnabled = Properties::skpCaptureEnabled;
+ if (mPictureCapturedCallback) {
+ Properties::skpCaptureEnabled = true;
+ }
+
renderVectorDrawableCache();
// draw all layers up front
@@ -334,6 +348,8 @@
ATRACE_NAME("flush commands");
surface->getCanvas()->flush();
+
+ Properties::skpCaptureEnabled = previousSkpEnabled;
}
namespace {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index cf6f5b2..e9957df 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -105,6 +105,11 @@
mLightCenter = lightGeometry.center;
}
+ void setPictureCapturedCallback(
+ const std::function<void(sk_sp<SkPicture>&&)>& callback) override {
+ mPictureCapturedCallback = callback;
+ }
+
protected:
void dumpResourceCacheUsage() const;
void setSurfaceColorProperties(renderthread::ColorMode colorMode);
@@ -163,6 +168,8 @@
* parallel tryCapture calls (not really needed).
*/
std::unique_ptr<SkPictureRecorder> mRecorder;
+ std::unique_ptr<SkNWayCanvas> mNwayCanvas;
+ std::function<void(sk_sp<SkPicture>&&)> mPictureCapturedCallback;
static float mLightRadius;
static uint8_t mAmbientShadowAlpha;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 9e7abf4..db97763 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -184,6 +184,10 @@
mFrameCompleteCallbacks.push_back(std::move(func));
}
+ void setPictureCapturedCallback(const std::function<void(sk_sp<SkPicture>&&)>& callback) {
+ mRenderPipeline->setPictureCapturedCallback(callback);
+ }
+
void setForceDark(bool enable) {
mUseForceDark = enable;
}
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index d4dd629..2cfc8df3 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -59,15 +59,15 @@
virtual MakeCurrentResult makeCurrent() = 0;
virtual Frame getFrame() = 0;
virtual bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
- const LightGeometry& lightGeometry,
- LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds,
- bool opaque, const LightInfo& lightInfo,
+ const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes,
FrameInfoVisualizer* profiler) = 0;
virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
virtual DeferredLayerUpdater* createTextureLayer() = 0;
- virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0;
+ virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior,
+ ColorMode colorMode) = 0;
virtual void onStop() = 0;
virtual bool isSurfaceReady() = 0;
virtual bool isContextReady() = 0;
@@ -85,6 +85,8 @@
virtual SkColorType getSurfaceColorType() const = 0;
virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0;
virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
+ virtual void setPictureCapturedCallback(
+ const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
virtual ~IRenderPipeline() {}
};
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index aa6af23..720c603 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -21,6 +21,7 @@
#include "Properties.h"
#include "Readback.h"
#include "Rect.h"
+#include "WebViewFunctorManager.h"
#include "pipeline/skia/SkiaOpenGLPipeline.h"
#include "pipeline/skia/VectorDrawableAtlas.h"
#include "renderstate/RenderState.h"
@@ -30,7 +31,6 @@
#include "renderthread/RenderThread.h"
#include "utils/Macros.h"
#include "utils/TimeUtils.h"
-#include "WebViewFunctorManager.h"
#include <ui/GraphicBuffer.h>
@@ -147,9 +147,7 @@
void RenderProxy::destroyFunctor(int functor) {
ATRACE_CALL();
RenderThread& thread = RenderThread::getInstance();
- thread.queue().post([=]() {
- WebViewFunctorManager::instance().destroyFunctor(functor);
- });
+ thread.queue().post([=]() { WebViewFunctorManager::instance().destroyFunctor(functor); });
}
DeferredLayerUpdater* RenderProxy::createTextureLayer() {
@@ -164,9 +162,9 @@
bool RenderProxy::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap) {
auto& thread = RenderThread::getInstance();
- return thread.queue().runSync(
- [&]() -> bool { return thread.readback().copyLayerInto(layer, &bitmap)
- == CopyResult::Success; });
+ return thread.queue().runSync([&]() -> bool {
+ return thread.readback().copyLayerInto(layer, &bitmap) == CopyResult::Success;
+ });
}
void RenderProxy::pushLayerUpdate(DeferredLayerUpdater* layer) {
@@ -204,9 +202,8 @@
}
int RenderProxy::maxTextureSize() {
- static int maxTextureSize = RenderThread::getInstance().queue().runSync([]() {
- return DeviceInfo::get()->maxTextureSize();
- });
+ static int maxTextureSize = RenderThread::getInstance().queue().runSync(
+ []() { return DeviceInfo::get()->maxTextureSize(); });
return maxTextureSize;
}
@@ -244,8 +241,10 @@
}
void RenderProxy::dumpGraphicsMemory(int fd) {
- auto& thread = RenderThread::getInstance();
- thread.queue().runSync([&]() { thread.dumpGraphicsMemory(fd); });
+ if (RenderThread::hasInstance()) {
+ auto& thread = RenderThread::getInstance();
+ thread.queue().runSync([&]() { thread.dumpGraphicsMemory(fd); });
+ }
}
void RenderProxy::setProcessStatsBuffer(int fd) {
@@ -281,6 +280,12 @@
mDrawFrameTask.setContentDrawBounds(left, top, right, bottom);
}
+void RenderProxy::setPictureCapturedCallback(
+ const std::function<void(sk_sp<SkPicture>&&)>& callback) {
+ mRenderThread.queue().post(
+ [ this, cb = callback ]() { mContext->setPictureCapturedCallback(cb); });
+}
+
void RenderProxy::setFrameCallback(std::function<void(int64_t)>&& callback) {
mDrawFrameTask.setFrameCallback(std::move(callback));
}
@@ -302,9 +307,7 @@
}
void RenderProxy::setForceDark(bool enable) {
- mRenderThread.queue().post([this, enable]() {
- mContext->setForceDark(enable);
- });
+ mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
}
int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom,
@@ -348,9 +351,8 @@
// TODO: fix everything that hits this. We should never be triggering a readback ourselves.
return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap);
} else {
- return thread.queue().runSync([&]() -> int {
- return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap);
- });
+ return thread.queue().runSync(
+ [&]() -> int { return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap); });
}
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 9dc9181..6e1bfd7 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -114,6 +114,8 @@
ANDROID_API void removeRenderNode(RenderNode* node);
ANDROID_API void drawRenderNode(RenderNode* node);
ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom);
+ ANDROID_API void setPictureCapturedCallback(
+ const std::function<void(sk_sp<SkPicture>&&)>& callback);
ANDROID_API void setFrameCallback(std::function<void(int64_t)>&& callback);
ANDROID_API void setFrameCompleteCallback(std::function<void(int64_t)>&& callback);
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 154bd56..3d0afb09 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -16,6 +16,8 @@
package android.location;
+import android.Manifest;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -161,6 +163,7 @@
private WorkSource mWorkSource = null;
@UnsupportedAppUsage
private boolean mHideFromAppOps = false; // True if this request shouldn't be counted by AppOps
+ private boolean mLocationSettingsIgnored = false;
@UnsupportedAppUsage
private String mProvider = LocationManager.FUSED_PROVIDER;
@@ -261,6 +264,7 @@
mWorkSource = src.mWorkSource;
mHideFromAppOps = src.mHideFromAppOps;
mLowPowerMode = src.mLowPowerMode;
+ mLocationSettingsIgnored = src.mLocationSettingsIgnored;
}
/**
@@ -375,6 +379,32 @@
}
/**
+ * Requests that user location settings be ignored in order to satisfy this request. This API
+ * is only for use in extremely rare scenarios where it is appropriate to ignore user location
+ * settings, such as a user initiated emergency (dialing 911 for instance).
+ *
+ * @param locationSettingsIgnored Whether to ignore location settings
+ * @return the same object, so that setters can be chained
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @SystemApi
+ public LocationRequest setLocationSettingsIgnored(boolean locationSettingsIgnored) {
+ mLocationSettingsIgnored = locationSettingsIgnored;
+ return this;
+ }
+
+ /**
+ * Returns true if location settings will be ignored in order to satisfy this request.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isLocationSettingsIgnored() {
+ return mLocationSettingsIgnored;
+ }
+
+ /**
* Explicitly set the fastest interval for location updates, in
* milliseconds.
*
diff --git a/location/lib/java/com/android/location/provider/ActivityChangedEvent.java b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java
new file mode 100644
index 0000000..843dd67
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 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.location.provider;
+
+import android.annotation.NonNull;
+
+import java.security.InvalidParameterException;
+import java.util.List;
+
+/**
+ * A class representing an event for Activity changes.
+ * @hide
+ */
+public class ActivityChangedEvent {
+ private final List<ActivityRecognitionEvent> mActivityRecognitionEvents;
+
+ public ActivityChangedEvent(List<ActivityRecognitionEvent> activityRecognitionEvents) {
+ if (activityRecognitionEvents == null) {
+ throw new InvalidParameterException(
+ "Parameter 'activityRecognitionEvents' must not be null.");
+ }
+
+ mActivityRecognitionEvents = activityRecognitionEvents;
+ }
+
+ @NonNull
+ public Iterable<ActivityRecognitionEvent> getActivityRecognitionEvents() {
+ return mActivityRecognitionEvents;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("[ ActivityChangedEvent:");
+
+ for (ActivityRecognitionEvent event : mActivityRecognitionEvents) {
+ builder.append("\n ");
+ builder.append(event.toString());
+ }
+ builder.append("\n]");
+
+ return builder.toString();
+ }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java
new file mode 100644
index 0000000..e54dea4
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 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.location.provider;
+
+/**
+ * A class that represents an Activity Recognition Event.
+ * @hide
+ */
+public class ActivityRecognitionEvent {
+ private final String mActivity;
+ private final int mEventType;
+ private final long mTimestampNs;
+
+ public ActivityRecognitionEvent(String activity, int eventType, long timestampNs) {
+ mActivity = activity;
+ mEventType = eventType;
+ mTimestampNs = timestampNs;
+ }
+
+ public String getActivity() {
+ return mActivity;
+ }
+
+ public int getEventType() {
+ return mEventType;
+ }
+
+ public long getTimestampNs() {
+ return mTimestampNs;
+ }
+
+ @Override
+ public String toString() {
+ String eventString;
+ switch (mEventType) {
+ case ActivityRecognitionProvider.EVENT_TYPE_ENTER:
+ eventString = "Enter";
+ break;
+ case ActivityRecognitionProvider.EVENT_TYPE_EXIT:
+ eventString = "Exit";
+ break;
+ case ActivityRecognitionProvider.EVENT_TYPE_FLUSH_COMPLETE:
+ eventString = "FlushComplete";
+ break;
+ default:
+ eventString = "<Invalid>";
+ break;
+ }
+
+ return String.format(
+ "Activity='%s', EventType=%s(%s), TimestampNs=%s",
+ mActivity,
+ eventString,
+ mEventType,
+ mTimestampNs);
+ }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java
new file mode 100644
index 0000000..0eff7d3
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2014 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.location.provider;
+
+import com.android.internal.util.Preconditions;
+
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareSink;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * A class that exposes {@link IActivityRecognitionHardware} functionality to unbundled services.
+ * @hide
+ */
+public final class ActivityRecognitionProvider {
+ private final IActivityRecognitionHardware mService;
+ private final HashSet<Sink> mSinkSet = new HashSet<>();
+
+ // the following constants must remain in sync with activity_recognition.h
+
+ public static final String ACTIVITY_IN_VEHICLE = "android.activity_recognition.in_vehicle";
+ public static final String ACTIVITY_ON_BICYCLE = "android.activity_recognition.on_bicycle";
+ public static final String ACTIVITY_WALKING = "android.activity_recognition.walking";
+ public static final String ACTIVITY_RUNNING = "android.activity_recognition.running";
+ public static final String ACTIVITY_STILL = "android.activity_recognition.still";
+ public static final String ACTIVITY_TILTING = "android.activity_recognition.tilting";
+
+ // NOTE: when adding an additional EVENT_TYPE_, EVENT_TYPE_COUNT needs to be updated in
+ // android.hardware.location.ActivityRecognitionHardware
+ public static final int EVENT_TYPE_FLUSH_COMPLETE = 0;
+ public static final int EVENT_TYPE_ENTER = 1;
+ public static final int EVENT_TYPE_EXIT = 2;
+
+ // end constants activity_recognition.h
+
+ /**
+ * Used to receive Activity-Recognition events.
+ */
+ public interface Sink {
+ void onActivityChanged(ActivityChangedEvent event);
+ }
+
+ public ActivityRecognitionProvider(IActivityRecognitionHardware service)
+ throws RemoteException {
+ Preconditions.checkNotNull(service);
+ mService = service;
+ mService.registerSink(new SinkTransport());
+ }
+
+ public String[] getSupportedActivities() throws RemoteException {
+ return mService.getSupportedActivities();
+ }
+
+ public boolean isActivitySupported(String activity) throws RemoteException {
+ return mService.isActivitySupported(activity);
+ }
+
+ public void registerSink(Sink sink) {
+ Preconditions.checkNotNull(sink);
+ synchronized (mSinkSet) {
+ mSinkSet.add(sink);
+ }
+ }
+
+ // TODO: if this functionality is exposed to 3rd party developers, handle unregistration (here
+ // and in the service) of all sinks while failing to disable all events
+ public void unregisterSink(Sink sink) {
+ Preconditions.checkNotNull(sink);
+ synchronized (mSinkSet) {
+ mSinkSet.remove(sink);
+ }
+ }
+
+ public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs)
+ throws RemoteException {
+ return mService.enableActivityEvent(activity, eventType, reportLatencyNs);
+ }
+
+ public boolean disableActivityEvent(String activity, int eventType) throws RemoteException {
+ return mService.disableActivityEvent(activity, eventType);
+ }
+
+ public boolean flush() throws RemoteException {
+ return mService.flush();
+ }
+
+ private final class SinkTransport extends IActivityRecognitionHardwareSink.Stub {
+ @Override
+ public void onActivityChanged(android.hardware.location.ActivityChangedEvent event) {
+ Collection<Sink> sinks;
+ synchronized (mSinkSet) {
+ if (mSinkSet.isEmpty()) {
+ return;
+ }
+ sinks = new ArrayList<>(mSinkSet);
+ }
+
+ // translate the event from platform internal and GmsCore types
+ ArrayList<ActivityRecognitionEvent> gmsEvents = new ArrayList<>();
+ for (android.hardware.location.ActivityRecognitionEvent reportingEvent
+ : event.getActivityRecognitionEvents()) {
+ ActivityRecognitionEvent gmsEvent = new ActivityRecognitionEvent(
+ reportingEvent.getActivity(),
+ reportingEvent.getEventType(),
+ reportingEvent.getTimestampNs());
+ gmsEvents.add(gmsEvent);
+ }
+ ActivityChangedEvent gmsEvent = new ActivityChangedEvent(gmsEvents);
+
+ for (Sink sink : sinks) {
+ sink.onActivityChanged(gmsEvent);
+ }
+ }
+ }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProviderClient.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderClient.java
new file mode 100644
index 0000000..326d901
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderClient.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 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.location.provider;
+
+import android.annotation.NonNull;
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareClient;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A client class for interaction with an Activity-Recognition provider.
+ * @hide
+ */
+public abstract class ActivityRecognitionProviderClient {
+ private static final String TAG = "ArProviderClient";
+
+ protected ActivityRecognitionProviderClient() {}
+
+ private IActivityRecognitionHardwareClient.Stub mClient =
+ new IActivityRecognitionHardwareClient.Stub() {
+ @Override
+ public void onAvailabilityChanged(
+ boolean isSupported,
+ IActivityRecognitionHardware instance) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ Log.d(TAG, "Ignoring calls from non-system server. Uid: " + callingUid);
+ return;
+ }
+ ActivityRecognitionProvider provider;
+ try {
+ provider = isSupported ? new ActivityRecognitionProvider(instance) : null;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error creating Hardware Activity-Recognition Provider.", e);
+ return;
+ }
+ onProviderChanged(isSupported, provider);
+ }
+ };
+
+ /**
+ * Gets the binder needed to interact with proxy provider in the platform.
+ */
+ @NonNull
+ public IBinder getBinder() {
+ return mClient;
+ }
+
+ /**
+ * Called when a change in the availability of {@link ActivityRecognitionProvider} is detected.
+ *
+ * @param isSupported whether the platform supports the provider natively
+ * @param instance the available provider's instance
+ */
+ public abstract void onProviderChanged(
+ boolean isSupported,
+ ActivityRecognitionProvider instance);
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java
new file mode 100644
index 0000000..42f77b4
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 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.location.provider;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareWatcher;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A watcher class for Activity-Recognition instances.
+ *
+ * @deprecated use {@link ActivityRecognitionProviderClient} instead.
+ * @hide
+ */
+@Deprecated
+public class ActivityRecognitionProviderWatcher {
+ private static final String TAG = "ActivityRecognitionProviderWatcher";
+
+ private static ActivityRecognitionProviderWatcher sWatcher;
+ private static final Object sWatcherLock = new Object();
+
+ private ActivityRecognitionProvider mActivityRecognitionProvider;
+
+ private ActivityRecognitionProviderWatcher() {}
+
+ public static ActivityRecognitionProviderWatcher getInstance() {
+ synchronized (sWatcherLock) {
+ if (sWatcher == null) {
+ sWatcher = new ActivityRecognitionProviderWatcher();
+ }
+ return sWatcher;
+ }
+ }
+
+ private IActivityRecognitionHardwareWatcher.Stub mWatcherStub =
+ new IActivityRecognitionHardwareWatcher.Stub() {
+ @Override
+ public void onInstanceChanged(IActivityRecognitionHardware instance) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ Log.d(TAG, "Ignoring calls from non-system server. Uid: " + callingUid);
+ return;
+ }
+
+ try {
+ mActivityRecognitionProvider = new ActivityRecognitionProvider(instance);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error creating Hardware Activity-Recognition", e);
+ }
+ }
+ };
+
+ /**
+ * Gets the binder needed to interact with proxy provider in the platform.
+ */
+ @NonNull
+ public IBinder getBinder() {
+ return mWatcherStub;
+ }
+
+ /**
+ * Gets an object that supports the functionality of {@link ActivityRecognitionProvider}.
+ *
+ * @return Non-null value if the functionality is supported by the platform, false otherwise.
+ */
+ @Nullable
+ public ActivityRecognitionProvider getActivityRecognitionProvider() {
+ return mActivityRecognitionProvider;
+ }
+}
diff --git a/lowpan/tests/Android.mk b/lowpan/tests/Android.mk
index 67727a7..832ed2f 100644
--- a/lowpan/tests/Android.mk
+++ b/lowpan/tests/Android.mk
@@ -45,7 +45,7 @@
LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-test \
+ androidx.test.rules \
guava \
mockito-target-minus-junit4 \
frameworks-base-testutils \
diff --git a/lowpan/tests/AndroidManifest.xml b/lowpan/tests/AndroidManifest.xml
index a216214..4225613 100644
--- a/lowpan/tests/AndroidManifest.xml
+++ b/lowpan/tests/AndroidManifest.xml
@@ -30,7 +30,7 @@
</activity>
</application>
- <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="android.net.lowpan.test"
android:label="Frameworks LoWPAN API Tests">
</instrumentation>
diff --git a/lowpan/tests/AndroidTest.xml b/lowpan/tests/AndroidTest.xml
index 72ad050..978cc02 100644
--- a/lowpan/tests/AndroidTest.xml
+++ b/lowpan/tests/AndroidTest.xml
@@ -22,6 +22,6 @@
<option name="test-tag" value="FrameworksLowpanApiTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.net.lowpan.test" />
- <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
</test>
</configuration>
diff --git a/lowpan/tests/README.md b/lowpan/tests/README.md
index d0eed95..cb5772e 100644
--- a/lowpan/tests/README.md
+++ b/lowpan/tests/README.md
@@ -37,7 +37,7 @@
If you manually build and push the test APK to the device you can run tests using
```
-adb shell am instrument -w 'android.net.wifi.test/android.support.test.runner.AndroidJUnitRunner'
+adb shell am instrument -w 'android.net.wifi.test/androidx.test.runner.AndroidJUnitRunner'
```
## Adding Tests
diff --git a/lowpan/tests/runtests.sh b/lowpan/tests/runtests.sh
index 040f4f0..8267a79 100755
--- a/lowpan/tests/runtests.sh
+++ b/lowpan/tests/runtests.sh
@@ -21,4 +21,4 @@
adb install -r -g "$OUT/data/app/FrameworksLowpanApiTests/FrameworksLowpanApiTests.apk"
-adb shell am instrument -w "$@" 'android.net.lowpan.test/android.support.test.runner.AndroidJUnitRunner'
+adb shell am instrument -w "$@" 'android.net.lowpan.test/androidx.test.runner.AndroidJUnitRunner'
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
index a495d3d..86f9d0e 100644
--- a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
+++ b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
@@ -23,15 +23,18 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.test.TestLooper;
-import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
-import java.util.Map;
+
+import androidx.test.runner.AndroidJUnit4;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Map;
+
/** Unit tests for android.net.lowpan.LowpanInterface. */
@RunWith(AndroidJUnit4.class)
@SmallTest
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
index 3dd7504..998e8a5 100644
--- a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
+++ b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
@@ -26,8 +26,10 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.test.TestLooper;
-import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/media/Android.bp b/media/Android.bp
index 0eb86ac..88ed9c6a 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -1,25 +1,59 @@
java_library {
- name: "media1",
+ name: "updatable-media1",
srcs: [
":media1-srcs",
+ ":framework-media-annotation-srcs",
],
+ aidl: {
+ export_include_dirs: [
+ "apex/java",
+ ],
+
+ // TODO: find out a way to include only the necessary aidl files instead of dirs.
+ include_dirs: [
+ "frameworks/base/core/java",
+ "frameworks/base/media/java",
+ ],
+ },
+
+ installable: true,
+
+ // Make sure that the implementaion only relies on SDK or system APIs.
sdk_version: "system_current",
}
-filegroup {
- name: "media1-srcs",
+java_library {
+ name: "updatable-mediasession2",
+
srcs: [
- "java/android/media/session/MediaSessionProviderService.java",
+ ":mediasession2-srcs",
+ ":framework-media-annotation-srcs",
],
+
+ aidl: {
+ export_include_dirs: [
+ "apex/java",
+ ],
+
+ // TODO: find out a way to include only the necessary aidl files instead of dirs.
+ include_dirs: [
+ "frameworks/base/core/java",
+ ],
+ },
+
+ installable: true,
+
+ // Make sure that the implementaion only relies on SDK or system APIs.
+ sdk_version: "system_current",
}
java_library {
name: "updatable-media",
srcs: [
- ":media2-srcs",
+ ":mediaplayer2-srcs",
":framework-media-annotation-srcs",
],
@@ -34,7 +68,88 @@
}
filegroup {
- name: "media2-srcs",
+ name: "media-srcs-without-aidls",
+ srcs : [
+ ":media1-srcs-without-aidls",
+ ":mediasession2-srcs-without-aidls",
+ ":mediaplayer2-srcs",
+ ],
+}
+
+filegroup {
+ name: "media1-srcs",
+ srcs: [
+ "apex/java/android/media/MediaMetadata.java",
+ "apex/java/android/media/MediaParceledListSlice.java",
+ "apex/java/android/media/VolumeProvider.java",
+ "apex/java/android/media/browse/MediaBrowser.java",
+ "apex/java/android/media/browse/MediaBrowserUtils.java",
+ "apex/java/android/media/session/ControllerCallbackLink.java",
+ "apex/java/android/media/session/ControllerLink.java",
+ "apex/java/android/media/session/ISession.aidl",
+ "apex/java/android/media/session/ISessionCallback.aidl",
+ "apex/java/android/media/session/ISessionController.aidl",
+ "apex/java/android/media/session/ISessionControllerCallback.aidl",
+ "apex/java/android/media/session/MediaController.java",
+ "apex/java/android/media/session/MediaSessionEngine.java",
+ "apex/java/android/media/session/MediaSessionProviderService.java",
+ "apex/java/android/media/session/PlaybackState.java",
+ "apex/java/android/media/session/SessionCallbackLink.java",
+ "apex/java/android/media/session/SessionLink.java",
+ "apex/java/android/service/media/IMediaBrowserService.aidl",
+ "apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl",
+ "apex/java/android/service/media/MediaBrowserService.java",
+ ],
+}
+
+filegroup {
+ name: "media1-srcs-without-aidls",
+ srcs: [
+ ":media1-srcs",
+ ],
+ exclude_srcs: [
+ "apex/java/android/media/session/ISession.aidl",
+ "apex/java/android/media/session/ISessionCallback.aidl",
+ "apex/java/android/media/session/ISessionController.aidl",
+ "apex/java/android/media/session/ISessionControllerCallback.aidl",
+ "apex/java/android/service/media/IMediaBrowserService.aidl",
+ "apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl",
+ ],
+}
+
+filegroup {
+ name: "mediasession2-srcs",
+ srcs: [
+ "apex/java/android/media/Controller2Link.java",
+ "apex/java/android/media/IMediaController2.aidl",
+ "apex/java/android/media/IMediaSession2.aidl",
+ "apex/java/android/media/IMediaSession2Service.aidl",
+ "apex/java/android/media/MediaConstants.java",
+ "apex/java/android/media/MediaController2.java",
+ "apex/java/android/media/MediaItem2.java",
+ "apex/java/android/media/MediaSession2.java",
+ "apex/java/android/media/MediaSession2Service.java",
+ "apex/java/android/media/Session2Command.java",
+ "apex/java/android/media/Session2CommandGroup.java",
+ "apex/java/android/media/Session2Link.java",
+ "apex/java/android/media/Session2Token.java",
+ ],
+}
+
+filegroup {
+ name: "mediasession2-srcs-without-aidls",
+ srcs: [
+ ":mediasession2-srcs",
+ ],
+ exclude_srcs: [
+ "apex/java/android/media/IMediaController2.aidl",
+ "apex/java/android/media/IMediaSession2.aidl",
+ "apex/java/android/media/IMediaSession2Service.aidl",
+ ],
+}
+
+filegroup {
+ name: "mediaplayer2-srcs",
srcs: [
"apex/java/android/media/CloseGuard.java",
"apex/java/android/media/DataSourceCallback.java",
diff --git a/media/java/android/media/Controller2Link.aidl b/media/apex/java/android/media/Controller2Link.aidl
similarity index 100%
rename from media/java/android/media/Controller2Link.aidl
rename to media/apex/java/android/media/Controller2Link.aidl
diff --git a/media/java/android/media/Controller2Link.java b/media/apex/java/android/media/Controller2Link.java
similarity index 100%
rename from media/java/android/media/Controller2Link.java
rename to media/apex/java/android/media/Controller2Link.java
diff --git a/media/java/android/media/IMediaController2.aidl b/media/apex/java/android/media/IMediaController2.aidl
similarity index 100%
rename from media/java/android/media/IMediaController2.aidl
rename to media/apex/java/android/media/IMediaController2.aidl
diff --git a/media/java/android/media/IMediaSession2.aidl b/media/apex/java/android/media/IMediaSession2.aidl
similarity index 100%
rename from media/java/android/media/IMediaSession2.aidl
rename to media/apex/java/android/media/IMediaSession2.aidl
diff --git a/media/java/android/media/IMediaSession2Service.aidl b/media/apex/java/android/media/IMediaSession2Service.aidl
similarity index 100%
rename from media/java/android/media/IMediaSession2Service.aidl
rename to media/apex/java/android/media/IMediaSession2Service.aidl
diff --git a/media/java/android/media/MediaConstants.java b/media/apex/java/android/media/MediaConstants.java
similarity index 100%
rename from media/java/android/media/MediaConstants.java
rename to media/apex/java/android/media/MediaConstants.java
diff --git a/media/java/android/media/MediaController2.java b/media/apex/java/android/media/MediaController2.java
similarity index 95%
rename from media/java/android/media/MediaController2.java
rename to media/apex/java/android/media/MediaController2.java
index dd97195..887b447 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/apex/java/android/media/MediaController2.java
@@ -45,7 +45,7 @@
/**
* Allows an app to interact with an active {@link MediaSession2} or a
- * MediaSession2Service which would provide {@link MediaSession2}. Media buttons and other
+ * {@link MediaSession2Service} which would provide {@link MediaSession2}. Media buttons and other
* commands can be sent to the session.
* <p>
* This API is not generally intended for third party application developers.
@@ -53,7 +53,6 @@
* <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
* for consistent behavior across all devices.
*/
-// TODO: use @link for MediaSession2Service
public class MediaController2 implements AutoCloseable {
static final String TAG = "MediaController2";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -71,6 +70,8 @@
private final Object mLock = new Object();
//@GuardedBy("mLock")
+ private boolean mClosed;
+ //@GuardedBy("mLock")
private int mNextSeqNumber;
//@GuardedBy("mLock")
private Session2Link mSessionBinder;
@@ -141,7 +142,14 @@
@Override
public void close() {
synchronized (mLock) {
+ if (mClosed) {
+ // Already closed. Ignore rest of clean up code.
+ // Note: unbindService() throws IllegalArgumentException when it's called twice.
+ return;
+ }
+ mClosed = true;
if (mServiceConnection != null) {
+ // Note: This should be called even when the bindService() has returned false.
mContext.unbindService(mServiceConnection);
}
if (mSessionBinder != null) {
@@ -167,7 +175,7 @@
* If it is not connected yet, it returns {@code null}.
* <p>
* This may differ with the {@link Session2Token} from the constructor. For example, if the
- * controller is created with the token for MediaSession2Service, this would return
+ * controller is created with the token for {@link MediaSession2Service}, this would return
* token for the {@link MediaSession2} in the service.
*
* @return Session2Token of the connected session, or {@code null} if not connected
@@ -316,7 +324,7 @@
MediaController2.this, command, args);
if (resultReceiver != null) {
if (result == null) {
- throw new RuntimeException("onSessionCommand shouldn't return null");
+ resultReceiver.send(Session2Command.RESULT_INFO_SKIPPED, null);
} else {
resultReceiver.send(result.getResultCode(), result.getResultData());
}
@@ -433,8 +441,8 @@
* @param controller the controller for this event
* @param command the session command
* @param args optional arguments
- * @return the result for the session command. A runtime exception will be thrown if null
- * is returned.
+ * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED
+ * will be sent to the session.
*/
@Nullable
public Session2Command.Result onSessionCommand(@NonNull MediaController2 controller,
diff --git a/media/java/android/media/MediaItem2.java b/media/apex/java/android/media/MediaItem2.java
similarity index 100%
rename from media/java/android/media/MediaItem2.java
rename to media/apex/java/android/media/MediaItem2.java
diff --git a/media/java/android/media/MediaMetadata.aidl b/media/apex/java/android/media/MediaMetadata.aidl
similarity index 100%
rename from media/java/android/media/MediaMetadata.aidl
rename to media/apex/java/android/media/MediaMetadata.aidl
diff --git a/media/java/android/media/MediaMetadata.java b/media/apex/java/android/media/MediaMetadata.java
similarity index 98%
rename from media/java/android/media/MediaMetadata.java
rename to media/apex/java/android/media/MediaMetadata.java
index a3d75a3..dea98d5 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/apex/java/android/media/MediaMetadata.java
@@ -250,16 +250,16 @@
* second line for media described by this metadata this should be preferred
* to other fields if present.
*/
- public static final String METADATA_KEY_DISPLAY_SUBTITLE
- = "android.media.metadata.DISPLAY_SUBTITLE";
+ public static final String METADATA_KEY_DISPLAY_SUBTITLE =
+ "android.media.metadata.DISPLAY_SUBTITLE";
/**
* A description that is suitable for display to the user. When displaying
* more information for media described by this metadata this should be
* preferred to other fields if present.
*/
- public static final String METADATA_KEY_DISPLAY_DESCRIPTION
- = "android.media.metadata.DISPLAY_DESCRIPTION";
+ public static final String METADATA_KEY_DISPLAY_DESCRIPTION =
+ "android.media.metadata.DISPLAY_DESCRIPTION";
/**
* An icon or thumbnail that is suitable for display to the user. When
@@ -270,8 +270,8 @@
* if it is too large. For higher resolution artwork
* {@link #METADATA_KEY_DISPLAY_ICON_URI} should be used instead.
*/
- public static final String METADATA_KEY_DISPLAY_ICON
- = "android.media.metadata.DISPLAY_ICON";
+ public static final String METADATA_KEY_DISPLAY_ICON =
+ "android.media.metadata.DISPLAY_ICON";
/**
* A Uri formatted String for an icon or thumbnail that is suitable for
@@ -285,8 +285,8 @@
* {@link ContentResolver#EXTRA_SIZE} for retrieving scaled artwork through
* {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}.
*/
- public static final String METADATA_KEY_DISPLAY_ICON_URI
- = "android.media.metadata.DISPLAY_ICON_URI";
+ public static final String METADATA_KEY_DISPLAY_ICON_URI =
+ "android.media.metadata.DISPLAY_ICON_URI";
/**
* A String key for identifying the content. This value is specific to the
@@ -320,8 +320,8 @@
* <li>{@link MediaDescription#BT_FOLDER_TYPE_YEARS}</li>
* </ul>
*/
- public static final String METADATA_KEY_BT_FOLDER_TYPE
- = "android.media.metadata.BT_FOLDER_TYPE";
+ public static final String METADATA_KEY_BT_FOLDER_TYPE =
+ "android.media.metadata.BT_FOLDER_TYPE";
private static final @TextKey String[] PREFERRED_DESCRIPTION_ORDER = {
METADATA_KEY_TITLE,
diff --git a/media/java/android/media/MediaParceledListSlice.aidl b/media/apex/java/android/media/MediaParceledListSlice.aidl
similarity index 100%
rename from media/java/android/media/MediaParceledListSlice.aidl
rename to media/apex/java/android/media/MediaParceledListSlice.aidl
diff --git a/media/java/android/media/MediaParceledListSlice.java b/media/apex/java/android/media/MediaParceledListSlice.java
similarity index 100%
rename from media/java/android/media/MediaParceledListSlice.java
rename to media/apex/java/android/media/MediaParceledListSlice.java
diff --git a/media/apex/java/android/media/MediaPlayer2.java b/media/apex/java/android/media/MediaPlayer2.java
index aa79c41..a2feec2 100644
--- a/media/apex/java/android/media/MediaPlayer2.java
+++ b/media/apex/java/android/media/MediaPlayer2.java
@@ -26,6 +26,7 @@
import android.content.res.AssetFileDescriptor;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
+import android.media.MediaDrm.KeyRequest;
import android.media.MediaPlayer2.DrmInfo;
import android.media.MediaPlayer2Proto.PlayerMessage;
import android.media.MediaPlayer2Proto.Value;
@@ -76,6 +77,8 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -298,7 +301,7 @@
private volatile float mVolume = 1.0f;
private VideoSize mVideoSize = new VideoSize(0, 0);
- private ExecutorService mDrmThreadPool = Executors.newCachedThreadPool();
+ private static ExecutorService sDrmThreadPool = Executors.newCachedThreadPool();
// Creating a dummy audio track, used for keeping session id alive
private final Object mSessionIdLock = new Object();
@@ -402,7 +405,7 @@
// Modular DRM clean up
synchronized (mDrmEventCallbackLock) {
- mDrmEventCallbackRecords.clear();
+ mDrmEventCallback = null;
}
native_release();
@@ -2293,6 +2296,7 @@
private static final int MEDIA_PAUSED = 7;
private static final int MEDIA_STOPPED = 8;
private static final int MEDIA_SKIPPED = 9;
+ private static final int MEDIA_DRM_PREPARED = 10;
private static final int MEDIA_NOTIFY_TIME = 98;
private static final int MEDIA_TIMED_TEXT = 99;
private static final int MEDIA_ERROR = 100;
@@ -2330,7 +2334,16 @@
switch(msg.what) {
case MEDIA_PREPARED:
+ case MEDIA_DRM_PREPARED:
{
+ sourceInfo.mPrepareBarrier--;
+ if (sourceInfo.mPrepareBarrier > 0) {
+ break;
+ } else if (sourceInfo.mPrepareBarrier < 0) {
+ Log.w(TAG, "duplicated (drm) prepared events");
+ break;
+ }
+
if (dsd != null) {
sendEvent(new EventNotifier() {
@Override
@@ -2387,14 +2400,42 @@
}
// notifying the client outside the lock
+ DrmPreparationInfo drmPrepareInfo = null;
if (drmInfo != null) {
- sendDrmEvent(new DrmEventNotifier() {
+ try {
+ drmPrepareInfo = sendDrmEventWait(
+ new DrmEventNotifier<DrmPreparationInfo>() {
+ @Override
+ public DrmPreparationInfo notifyWait(
+ DrmEventCallback callback) {
+ return callback.onDrmInfo(mMediaPlayer, dsd,
+ drmInfo);
+ }
+ });
+ } catch (InterruptedException | ExecutionException
+ | TimeoutException e) {
+ Log.w(TAG, "Exception while waiting for DrmPreparationInfo", e);
+ }
+ }
+ if (sourceInfo.mDrmHandle.setPreparationInfo(drmPrepareInfo)) {
+ sourceInfo.mPrepareBarrier++;
+ final Task prepareDrmTask;
+ prepareDrmTask = newPrepareDrmTask(dsd, drmPrepareInfo.mUUID);
+ mTaskHandler.post(new Runnable() {
@Override
- public void notify(DrmEventCallback callback) {
- callback.onDrmInfo(
- mMediaPlayer, dsd, drmInfo);
+ public void run() {
+ // Run as simple Runnable, not Task
+ try {
+ prepareDrmTask.process();
+ } catch (NoDrmSchemeException | IOException e) {
+ final String errMsg;
+ errMsg = "Unexpected Exception during prepareDrm";
+ throw new RuntimeException(errMsg, e);
+ }
}
});
+ } else {
+ Log.w(TAG, "No valid DrmPreparationInfo set");
}
} else {
Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj);
@@ -2892,7 +2933,8 @@
private void sendDrmEvent(final DrmEventNotifier notifier) {
synchronized (mDrmEventCallbackLock) {
try {
- for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
+ Pair<Executor, DrmEventCallback> cb = mDrmEventCallback;
+ if (cb != null) {
cb.first.execute(() -> notifier.notify(cb.second));
}
} catch (RejectedExecutionException e) {
@@ -2903,13 +2945,18 @@
}
private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier)
- throws InterruptedException, ExecutionException {
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return sendDrmEventWait(notifier, 0);
+ }
+
+ private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier, final long timeoutMs)
+ throws InterruptedException, ExecutionException, TimeoutException {
synchronized (mDrmEventCallbackLock) {
- mDrmEventCallbackRecords.get(0);
- for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
+ Pair<Executor, DrmEventCallback> cb = mDrmEventCallback;
+ if (cb != null) {
CompletableFuture<T> ret = new CompletableFuture<>();
cb.first.execute(() -> ret.complete(notifier.notifyWait(cb.second)));
- return ret.get();
+ return timeoutMs <= 0 ? ret.get() : ret.get(timeoutMs, TimeUnit.MILLISECONDS);
}
}
return null;
@@ -3388,8 +3435,8 @@
private Map<String, String> mOptionalParameters;
/**
- * Set UUID of the crypto scheme selected to decrypt content. An UUID can be retrieved from
- * the source listening to {@link MediaPlayer2.DrmEventCallback#onDrmInfo}.
+ * Set UUID of the crypto scheme selected to decrypt content. An UUID can be retrieved
+ * from the source listening to {@link MediaPlayer2.DrmEventCallback#onDrmInfo}.
*
* @param uuid of selected crypto scheme
* @return this
@@ -3401,11 +3448,12 @@
/**
* Set identifier of a persisted offline key obtained from
- * {@link MediaPlayer2.DrmEventCallback#onDrmPrepared(MediaPlayer2, DataSourceDesc, int, byte[])}.
+ * {@link MediaPlayer2.DrmEventCallback#onDrmPrepared}.
*
* A {@code keySetId} can be used to restore persisted offline keys into a new playback
- * session of a DRM protected data source. When {@code keySetId} is set, {@code initData},
- * {@code mimeType}, {@code keyType}, {@code optionalParameters} are ignored.
+ * session of a DRM protected data source. When {@code keySetId} is set,
+ * {@code initData}, {@code mimeType}, {@code keyType}, {@code optionalParameters} are
+ * ignored.
*
* @param keySetId identifier of a persisted offline key
* @return this
@@ -3455,24 +3503,24 @@
}
/**
- * Set optional parameters to be included in a {@link MediaDrm.KeyRequest} message sent to
- * the license server.
+ * Set optional parameters to be included in a {@link MediaDrm.KeyRequest} message sent
+ * to the license server.
*
* @param optionalParameters optional parameters to be included in a key request
* @return this
*/
- public Builder setOptionalParameters(
- @Nullable Map<String, String> optionalParameters) {
+ public Builder setOptionalParameters(@Nullable Map<String, String> optionalParameters) {
this.mOptionalParameters = optionalParameters;
return this;
}
/**
- * @return an immutable {@link MediaPlayer2.DrmPreparationInfo} representing the settings of this builder
+ * @return an immutable {@link MediaPlayer2.DrmPreparationInfo} representing the
+ * settings of this builder
*/
public MediaPlayer2.DrmPreparationInfo build() {
- return new MediaPlayer2.DrmPreparationInfo(mUUID, mKeySetId, mInitData, mMimeType, mKeyType,
- mOptionalParameters);
+ return new MediaPlayer2.DrmPreparationInfo(mUUID, mKeySetId, mInitData, mMimeType,
+ mKeyType, mOptionalParameters);
}
}
@@ -3494,6 +3542,20 @@
this.mOptionalParameters = optionalParameters;
}
+ boolean isValid() {
+ if (mUUID == null) {
+ return false;
+ }
+ if (mKeySetId != null) {
+ // offline restore case
+ return true;
+ }
+ if (mInitData != null && mMimeType != null) {
+ // new streaming license case
+ return true;
+ }
+ return false;
+ }
}
/**
@@ -3501,6 +3563,7 @@
* DRM events.
*/
public static class DrmEventCallback {
+
/**
* Called to indicate DRM info is available. Return a {@link DrmPreparationInfo} object that
* bundles DRM initialization parameters.
@@ -3517,21 +3580,6 @@
}
/**
- * Called to notify the client that {@code mp} is ready to decrypt DRM protected data source
- * {@code dsd}
- *
- * @param mp the {@code MediaPlayer2} associated with this callback
- * @param dsd the {@link DataSourceDesc} of this data source
- * @param status the result of DRM preparation.
- * @param keySetId optional identifier that can be used to restore DRM playback initiated
- * with a {@link MediaDrm#KEY_TYPE_OFFLINE} key request.
- *
- * @see DrmPreparationInfo.Builder#setKeySetId(byte[])
- */
- public void onDrmPrepared(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
- @PrepareDrmStatusCode int status, @Nullable byte[] keySetId) { }
-
- /**
* Called to give the app the opportunity to configure DRM before the session is created.
*
* This facilitates configuration of the properties, like 'securityLevel', which
@@ -3567,11 +3615,25 @@
return null;
}
+ /**
+ * Called to notify the client that {@code mp} is ready to decrypt DRM protected data source
+ * {@code dsd} or if there is an error during DRM preparation
+ *
+ * @param mp the {@code MediaPlayer2} associated with this callback
+ * @param dsd the {@link DataSourceDesc} of this data source
+ * @param status the result of DRM preparation.
+ * @param keySetId optional identifier that can be used to restore DRM playback initiated
+ * with a {@link MediaDrm#KEY_TYPE_OFFLINE} key request.
+ *
+ * @see DrmPreparationInfo.Builder#setKeySetId(byte[])
+ */
+ public void onDrmPrepared(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
+ @PrepareDrmStatusCode int status, @Nullable byte[] keySetId) { }
+
}
private final Object mDrmEventCallbackLock = new Object();
- private List<Pair<Executor, DrmEventCallback>> mDrmEventCallbackRecords =
- new ArrayList<Pair<Executor, DrmEventCallback>>();
+ private Pair<Executor, DrmEventCallback> mDrmEventCallback;
/**
* Registers the callback to be invoked for various DRM events.
@@ -3590,25 +3652,17 @@
"Illegal null Executor for the EventCallback");
}
synchronized (mDrmEventCallbackLock) {
- mDrmEventCallbackRecords = Collections.singletonList(
- new Pair<Executor, DrmEventCallback>(executor, eventCallback));
+ mDrmEventCallback = new Pair<Executor, DrmEventCallback>(executor, eventCallback);
}
}
/**
- * Unregisters the {@link DrmEventCallback}.
- *
- * @param eventCallback the callback to be unregistered
- * @hide
+ * Clear the {@link DrmEventCallback}.
*/
// This is a synchronous call.
- public void unregisterDrmEventCallback(DrmEventCallback eventCallback) {
+ public void clearDrmEventCallback() {
synchronized (mDrmEventCallbackLock) {
- for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
- if (cb.second == eventCallback) {
- mDrmEventCallbackRecords.remove(cb);
- }
- }
+ mDrmEventCallback = null;
}
}
@@ -3651,6 +3705,18 @@
*/
public static final int PREPARE_DRM_STATUS_RESOURCE_BUSY = 5;
+ /**
+ * Restoring persisted offline keys failed.
+ * @hide
+ */
+ public static final int PREPARE_DRM_STATUS_RESTORE_ERROR = 6;
+
+ /**
+ * Error during key request/response exchange with license server.
+ * @hide
+ */
+ public static final int PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR = 7;
+
/** @hide */
@IntDef(flag = false, prefix = "PREPARE_DRM_STATUS", value = {
PREPARE_DRM_STATUS_SUCCESS,
@@ -3659,6 +3725,8 @@
PREPARE_DRM_STATUS_PREPARATION_ERROR,
PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME,
PREPARE_DRM_STATUS_RESOURCE_BUSY,
+ PREPARE_DRM_STATUS_RESTORE_ERROR,
+ PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR,
})
@Retention(RetentionPolicy.SOURCE)
public @interface PrepareDrmStatusCode {}
@@ -3747,12 +3815,16 @@
*/
// This is an asynchronous call.
public Object prepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid) {
- return addTask(new Task(CALL_COMPLETED_PREPARE_DRM, true) {
+ return addTask(newPrepareDrmTask(dsd, uuid));
+ }
+
+ private Task newPrepareDrmTask(DataSourceDesc dsd, UUID uuid) {
+ return new Task(CALL_COMPLETED_PREPARE_DRM, true) {
@Override
void process() {
final SourceInfo sourceInfo = getSourceInfo(dsd);
int status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
- boolean sendEvent = true;
+ boolean finishPrepare = true;
if (sourceInfo == null) {
Log.e(TAG, "prepareDrm(): DataSource not found.");
@@ -3780,8 +3852,8 @@
status = sourceInfo.mDrmHandle.handleProvisioninig(uuid, mTaskId);
if (status == PREPARE_DRM_STATUS_SUCCESS) {
- // DrmEventCallback will be fired in provisioning
- sendEvent = false;
+ // License will be setup in provisioning
+ finishPrepare = false;
} else {
synchronized (sourceInfo.mDrmHandle) {
sourceInfo.mDrmHandle.cleanDrmObj();
@@ -3808,23 +3880,16 @@
status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
}
- if (sendEvent) {
- final int prepareDrmStatus = status;
- sendDrmEvent(new DrmEventNotifier() {
- @Override
- public void notify(DrmEventCallback callback) {
- callback.onDrmPrepared(MediaPlayer2.this, dsd, prepareDrmStatus,
- /* TODO: keySetId */ null);
- }
- });
-
+ if (finishPrepare) {
+ sourceInfo.mDrmHandle.finishPrepare(status);
synchronized (mTaskLock) {
mCurrentTask = null;
processPendingTask_l();
}
}
+
}
- });
+ };
}
/**
@@ -4417,7 +4482,7 @@
};
// Modular DRM
- final class DrmHandle {
+ private class DrmHandle {
static final int PROVISION_TIMEOUT_MS = 60000;
@@ -4432,6 +4497,7 @@
boolean mDrmProvisioningInProgress;
boolean mPrepareDrmInProgress;
Future<?> mProvisionResult;
+ DrmPreparationInfo mPrepareInfo;
//--- guarded by |this| end
DrmHandle(DataSourceDesc dsd, long srcId) {
@@ -4441,7 +4507,7 @@
void prepare(UUID uuid) throws UnsupportedSchemeException,
ResourceBusyException, NotProvisionedException, InterruptedException,
- ExecutionException {
+ ExecutionException, TimeoutException {
Log.v(TAG, "prepareDrm: uuid: " + uuid);
synchronized (this) {
@@ -4580,7 +4646,7 @@
// networking in a background thread
mDrmProvisioningInProgress = true;
- mProvisionResult = mDrmThreadPool.submit(newProvisioningTask(uuid, taskId));
+ mProvisionResult = sDrmThreadPool.submit(newProvisioningTask(uuid, taskId));
return PREPARE_DRM_STATUS_SUCCESS;
}
@@ -4654,14 +4720,7 @@
} // synchronized
// calling the callback outside the lock
- final int finalStatus = status;
- sendDrmEvent(new DrmEventNotifier() {
- @Override
- public void notify(DrmEventCallback callback) {
- callback.onDrmPrepared(
- MediaPlayer2.this, mDSD, finalStatus, /* TODO: keySetId */ null);
- }
- });
+ finishPrepare(status);
synchronized (mTaskLock) {
if (mCurrentTask != null
@@ -4703,6 +4762,93 @@
return success;
}
+ synchronized boolean setPreparationInfo(DrmPreparationInfo prepareInfo) {
+ if (prepareInfo == null || !prepareInfo.isValid() || mPrepareInfo != null) {
+ return false;
+ }
+ mPrepareInfo = prepareInfo;
+ return true;
+ }
+
+ void finishPrepare(int status) {
+ if (status != PREPARE_DRM_STATUS_SUCCESS) {
+ notifyPrepared(status, null);
+ return;
+ }
+
+ if (mPrepareInfo == null) {
+ // Deprecated: this can only happen when using MediaPlayer Version 1 APIs
+ notifyPrepared(status, null);
+ return;
+ }
+
+ final byte[] keySetId = mPrepareInfo.mKeySetId;
+ if (keySetId != null) {
+ try {
+ mDrmObj.restoreKeys(mDrmSessionId, keySetId);
+ notifyPrepared(PREPARE_DRM_STATUS_SUCCESS, keySetId);
+ } catch (Exception e) {
+ notifyPrepared(PREPARE_DRM_STATUS_RESTORE_ERROR, keySetId);
+ }
+ return;
+ }
+
+ sDrmThreadPool.submit(newKeyExchangeTask());
+ }
+
+ Runnable newKeyExchangeTask() {
+ return new Runnable() {
+ @Override
+ public void run() {
+ final byte[] initData = mPrepareInfo.mInitData;
+ final String mimeType = mPrepareInfo.mMimeType;
+ final int keyType = mPrepareInfo.mKeyType;
+ final Map<String, String> optionalParams = mPrepareInfo.mOptionalParameters;
+ byte[] keySetId = null;
+ try {
+ KeyRequest req;
+ req = getDrmKeyRequest(null, initData, mimeType, keyType, optionalParams);
+ byte[] response = sendDrmEventWait(new DrmEventNotifier<byte[]>() {
+ @Override
+ public byte[] notifyWait(DrmEventCallback callback) {
+ final MediaPlayer2 mp = MediaPlayer2.this;
+ return callback.onDrmKeyRequest(mp, mDSD, req);
+ }
+ });
+ keySetId = provideDrmKeyResponse(null, response);
+ } catch (Exception e) {
+ }
+ if (keySetId == null) {
+ notifyPrepared(PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR, null);
+ } else {
+ notifyPrepared(PREPARE_DRM_STATUS_SUCCESS, keySetId);
+ }
+ }
+ };
+ }
+
+ void notifyPrepared(final int status, byte[] keySetId) {
+
+ Message msg;
+ if (status == PREPARE_DRM_STATUS_SUCCESS) {
+ msg = mTaskHandler.obtainMessage(
+ MEDIA_DRM_PREPARED, 0, 0, null);
+ } else {
+ msg = mTaskHandler.obtainMessage(
+ MEDIA_ERROR, status, MEDIA_ERROR_UNKNOWN, null);
+ }
+ mTaskHandler.sendMessage(msg);
+
+ sendDrmEvent(new DrmEventNotifier() {
+ @Override
+ public void notify(DrmEventCallback callback) {
+ callback.onDrmPrepared(MediaPlayer2.this, mDSD, status,
+ keySetId);
+ }
+ });
+
+ }
+
void cleanDrmObj() {
// the caller holds mDrmLock
Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
@@ -4768,6 +4914,7 @@
// set to false to avoid duplicate release calls
this.mActiveDrmUUID = null;
+ native_releaseDrm(mSrcId);
cleanDrmObj();
} // synchronized
}
@@ -4924,6 +5071,7 @@
final long mId = mSrcIdGenerator.getAndIncrement();
AtomicInteger mBufferedPercentage = new AtomicInteger(0);
boolean mClosed = false;
+ int mPrepareBarrier = 1;
// m*AsNextSource (below) only applies to pending data sources in the playlist;
// the meanings of mCurrentSourceInfo.{mStateAsNextSource,mPlayPendingAsNextSource}
@@ -5022,7 +5170,7 @@
if (sourceInfo != null) {
sourceInfo.close();
Runnable task = sourceInfo.mDrmHandle.newCleanupTask();
- mDrmThreadPool.submit(task);
+ sDrmThreadPool.submit(task);
}
}
diff --git a/media/java/android/media/MediaSession2.java b/media/apex/java/android/media/MediaSession2.java
similarity index 94%
rename from media/java/android/media/MediaSession2.java
rename to media/apex/java/android/media/MediaSession2.java
index 3adac72..fdd07fd 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/apex/java/android/media/MediaSession2.java
@@ -90,6 +90,8 @@
private boolean mClosed;
//@GuardedBy("mLock")
private boolean mPlaybackActive;
+ //@GuardedBy("mLock")
+ private ForegroundServiceEventCallback mForegroundServiceEventCallback;
MediaSession2(@NonNull Context context, @NonNull String id, PendingIntent sessionActivity,
@NonNull Executor callbackExecutor, @NonNull SessionCallback callback) {
@@ -119,6 +121,7 @@
public void close() {
try {
List<ControllerInfo> controllerInfos;
+ ForegroundServiceEventCallback callback;
synchronized (mLock) {
if (mClosed) {
return;
@@ -126,11 +129,15 @@
mClosed = true;
controllerInfos = getConnectedControllers();
mConnectedControllers.clear();
- mCallback.onSessionClosed(this);
+ callback = mForegroundServiceEventCallback;
+ mForegroundServiceEventCallback = null;
}
synchronized (MediaSession2.class) {
SESSION_ID_LIST.remove(mSessionId);
}
+ if (callback != null) {
+ callback.onSessionClosed(this);
+ }
for (ControllerInfo info : controllerInfos) {
info.notifyDisconnected();
}
@@ -224,11 +231,16 @@
* @param playbackActive {@code true} if the playback active, {@code false} otherwise.
**/
public void setPlaybackActive(boolean playbackActive) {
+ final ForegroundServiceEventCallback serviceCallback;
synchronized (mLock) {
if (mPlaybackActive == playbackActive) {
return;
}
mPlaybackActive = playbackActive;
+ serviceCallback = mForegroundServiceEventCallback;
+ }
+ if (serviceCallback != null) {
+ serviceCallback.onPlaybackActiveChanged(this, playbackActive);
}
List<ControllerInfo> controllerInfos = getConnectedControllers();
for (ControllerInfo controller : controllerInfos) {
@@ -257,6 +269,18 @@
return mCallback;
}
+ void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) {
+ synchronized (mLock) {
+ if (mForegroundServiceEventCallback == callback) {
+ return;
+ }
+ if (mForegroundServiceEventCallback != null && callback != null) {
+ throw new IllegalStateException("A session cannot be added to multiple services");
+ }
+ mForegroundServiceEventCallback = callback;
+ }
+ }
+
// Called by Session2Link.onConnect and MediaSession2Service.MediaSession2ServiceStub.connect
void onConnect(final Controller2Link controller, int callingPid, int callingUid, int seq,
Bundle connectionRequest) {
@@ -373,7 +397,7 @@
MediaSession2.this, controllerInfo, command, args);
if (resultReceiver != null) {
if (result == null) {
- throw new RuntimeException("onSessionCommand shouldn't return null");
+ resultReceiver.send(Session2Command.RESULT_INFO_SKIPPED, null);
} else {
resultReceiver.send(result.getResultCode(), result.getResultData());
}
@@ -695,8 +719,6 @@
* This API is not generally intended for third party application developers.
*/
public abstract static class SessionCallback {
- ForegroundServiceEventCallback mForegroundServiceEventCallback;
-
/**
* Called when a controller is created for this session. Return allowed commands for
* controller. By default it returns {@code null}.
@@ -731,8 +753,8 @@
* @param controller controller information
* @param command the session command
* @param args optional arguments
- * @return the result for the session command. A runtime exception will be thrown if null
- * is returned.
+ * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED
+ * will be sent to the session.
*/
@Nullable
public Session2Command.Result onSessionCommand(@NonNull MediaSession2 session,
@@ -753,19 +775,10 @@
public void onCommandResult(@NonNull MediaSession2 session,
@NonNull ControllerInfo controller, @NonNull Object token,
@NonNull Session2Command command, @NonNull Session2Command.Result result) {}
+ }
- final void onSessionClosed(MediaSession2 session) {
- if (mForegroundServiceEventCallback != null) {
- mForegroundServiceEventCallback.onSessionClosed(session);
- }
- }
-
- void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) {
- mForegroundServiceEventCallback = callback;
- }
-
- abstract static class ForegroundServiceEventCallback {
- public void onSessionClosed(MediaSession2 session) {}
- }
+ abstract static class ForegroundServiceEventCallback {
+ public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {}
+ public void onSessionClosed(MediaSession2 session) {}
}
}
diff --git a/media/java/android/media/MediaSession2Service.java b/media/apex/java/android/media/MediaSession2Service.java
similarity index 63%
rename from media/java/android/media/MediaSession2Service.java
rename to media/apex/java/android/media/MediaSession2Service.java
index 8fb00fe..5bb746a 100644
--- a/media/java/android/media/MediaSession2Service.java
+++ b/media/apex/java/android/media/MediaSession2Service.java
@@ -19,7 +19,10 @@
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
@@ -28,8 +31,6 @@
import android.util.ArrayMap;
import android.util.Log;
-import com.android.internal.annotations.GuardedBy;
-
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -42,11 +43,7 @@
* Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
* <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
* for consistent behavior across all devices.
- * @hide
*/
-// TODO: Unhide
-// TODO: Add onUpdateNotification(), and calls it to get Notification for startForegroundService()
-// when a session's player state becomes playing.
public abstract class MediaSession2Service extends Service {
/**
* The {@link Intent} that must be declared as handled by the service.
@@ -56,10 +53,29 @@
private static final String TAG = "MediaSession2Service";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private final Object mLock = new Object();
- @GuardedBy("mLock")
- private Map<String, MediaSession2> mSessions = new ArrayMap<>();
+ private final MediaSession2.ForegroundServiceEventCallback mForegroundServiceEventCallback =
+ new MediaSession2.ForegroundServiceEventCallback() {
+ @Override
+ public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {
+ MediaSession2Service.this.onPlaybackActiveChanged(session, playbackActive);
+ }
+ @Override
+ public void onSessionClosed(MediaSession2 session) {
+ removeSession(session);
+ }
+ };
+
+ private final Object mLock = new Object();
+ //@GuardedBy("mLock")
+ private NotificationManager mNotificationManager;
+ //@GuardedBy("mLock")
+ private Intent mStartSelfIntent;
+ //@GuardedBy("mLock")
+ private Map<String, MediaSession2> mSessions = new ArrayMap<>();
+ //@GuardedBy("mLock")
+ private Map<MediaSession2, MediaNotification> mNotifications = new ArrayMap<>();
+ //@GuardedBy("mLock")
private MediaSession2ServiceStub mStub;
/**
@@ -72,7 +88,12 @@
@Override
public void onCreate() {
super.onCreate();
- mStub = new MediaSession2ServiceStub(this);
+ synchronized (mLock) {
+ mStub = new MediaSession2ServiceStub(this);
+ mStartSelfIntent = new Intent(this, this.getClass());
+ mNotificationManager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ }
}
@CallSuper
@@ -80,18 +101,13 @@
@Nullable
public IBinder onBind(@NonNull Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
- return mStub;
+ synchronized (mLock) {
+ return mStub;
+ }
}
return null;
}
- @CallSuper
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- // TODO: Dispatch media key events to the primary session.
- return START_STICKY;
- }
-
/**
* Called by the system to notify that it is no longer used and is being removed. Do not call
* this method directly.
@@ -104,10 +120,12 @@
public void onDestroy() {
super.onDestroy();
synchronized (mLock) {
- for (MediaSession2 session : mSessions.values()) {
- session.getCallback().setForegroundServiceEventCallback(null);
+ List<MediaSession2> sessions = getSessions();
+ for (MediaSession2 session : sessions) {
+ removeSession(session);
}
mSessions.clear();
+ mNotifications.clear();
}
mStub.close();
}
@@ -144,6 +162,24 @@
public abstract MediaSession2 onGetPrimarySession();
/**
+ * Called when notification UI needs update. Override this method to show or cancel your own
+ * notification UI.
+ * <p>
+ * This would be called on {@link MediaSession2}'s callback executor when playback state is
+ * changed.
+ * <p>
+ * With the notification returned here, the service becomes foreground service when the playback
+ * is started. Apps must request the permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use this API. It becomes
+ * background service after the playback is stopped.
+ *
+ * @param session a session that needs notification update.
+ * @return a {@link MediaNotification}. Can be {@code null}.
+ */
+ @Nullable
+ public abstract MediaNotification onUpdateNotification(@NonNull MediaSession2 session);
+
+ /**
* Adds a session to this service.
* <p>
* Added session will be removed automatically when it's closed, or removed when
@@ -161,21 +197,15 @@
}
synchronized (mLock) {
MediaSession2 previousSession = mSessions.get(session.getSessionId());
- if (previousSession != session) {
- if (previousSession != null) {
+ if (previousSession != null) {
+ if (previousSession != session) {
Log.w(TAG, "Session ID should be unique, ID=" + session.getSessionId()
+ ", previous=" + previousSession + ", session=" + session);
}
return;
}
mSessions.put(session.getSessionId(), session);
- session.getCallback().setForegroundServiceEventCallback(
- new MediaSession2.SessionCallback.ForegroundServiceEventCallback() {
- @Override
- public void onSessionClosed(MediaSession2 session) {
- removeSession(session);
- }
- });
+ session.setForegroundServiceEventCallback(mForegroundServiceEventCallback);
}
}
@@ -189,8 +219,21 @@
if (session == null) {
throw new IllegalArgumentException("session shouldn't be null");
}
+ MediaNotification notification;
synchronized (mLock) {
+ if (mSessions.get(session.getSessionId()) != session) {
+ // Session isn't added or removed already.
+ return;
+ }
mSessions.remove(session.getSessionId());
+ notification = mNotifications.remove(session);
+ }
+ session.setForegroundServiceEventCallback(null);
+ if (notification != null) {
+ mNotificationManager.cancel(notification.getNotificationId());
+ }
+ if (getSessions().isEmpty()) {
+ stopForeground(false);
}
}
@@ -207,6 +250,78 @@
return list;
}
+ /**
+ * Called by registered {@link MediaSession2.ForegroundServiceEventCallback}
+ *
+ * @param session session with change
+ * @param playbackActive {@code true} if playback is active.
+ */
+ void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {
+ MediaNotification mediaNotification = onUpdateNotification(session);
+ if (mediaNotification == null) {
+ // The service implementation doesn't want to use the automatic start/stopForeground
+ // feature.
+ return;
+ }
+ synchronized (mLock) {
+ mNotifications.put(session, mediaNotification);
+ }
+ int id = mediaNotification.getNotificationId();
+ Notification notification = mediaNotification.getNotification();
+ if (!playbackActive) {
+ mNotificationManager.notify(id, notification);
+ return;
+ }
+ // playbackActive == true
+ startForegroundService(mStartSelfIntent);
+ startForeground(id, notification);
+ }
+
+ /**
+ * Returned by {@link #onUpdateNotification(MediaSession2)} for making session service
+ * foreground service to keep playback running in the background. It's highly recommended to
+ * show media style notification here.
+ */
+ public static class MediaNotification {
+ private final int mNotificationId;
+ private final Notification mNotification;
+
+ /**
+ * Default constructor
+ *
+ * @param notificationId notification id to be used for
+ * {@link NotificationManager#notify(int, Notification)}.
+ * @param notification a notification to make session service run in the foreground. Media
+ * style notification is recommended here.
+ */
+ public MediaNotification(int notificationId, @NonNull Notification notification) {
+ if (notification == null) {
+ throw new IllegalArgumentException("notification shouldn't be null");
+ }
+ mNotificationId = notificationId;
+ mNotification = notification;
+ }
+
+ /**
+ * Gets the id of the notification.
+ *
+ * @return the notification id
+ */
+ public int getNotificationId() {
+ return mNotificationId;
+ }
+
+ /**
+ * Gets the notification.
+ *
+ * @return the notification
+ */
+ @NonNull
+ public Notification getNotification() {
+ return mNotification;
+ }
+ }
+
private static final class MediaSession2ServiceStub extends IMediaSession2Service.Stub
implements AutoCloseable {
final WeakReference<MediaSession2Service> mService;
diff --git a/media/java/android/media/Session2Command.aidl b/media/apex/java/android/media/Session2Command.aidl
similarity index 100%
rename from media/java/android/media/Session2Command.aidl
rename to media/apex/java/android/media/Session2Command.aidl
diff --git a/media/java/android/media/Session2Command.java b/media/apex/java/android/media/Session2Command.java
similarity index 100%
rename from media/java/android/media/Session2Command.java
rename to media/apex/java/android/media/Session2Command.java
diff --git a/media/java/android/media/Session2CommandGroup.java b/media/apex/java/android/media/Session2CommandGroup.java
similarity index 96%
rename from media/java/android/media/Session2CommandGroup.java
rename to media/apex/java/android/media/Session2CommandGroup.java
index a189c26..2dab697 100644
--- a/media/java/android/media/Session2CommandGroup.java
+++ b/media/apex/java/android/media/Session2CommandGroup.java
@@ -71,11 +71,10 @@
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
Session2CommandGroup(Parcel in) {
- Session2Command[] commands = in.readParcelableArray(
- Session2Command.class.getClassLoader(), Session2Command.class);
+ Parcelable[] commands = in.readParcelableArray(Session2Command.class.getClassLoader());
if (commands != null) {
- for (Session2Command command : commands) {
- mCommands.add(command);
+ for (Parcelable command : commands) {
+ mCommands.add((Session2Command) command);
}
}
}
diff --git a/media/java/android/media/Session2Link.java b/media/apex/java/android/media/Session2Link.java
similarity index 100%
rename from media/java/android/media/Session2Link.java
rename to media/apex/java/android/media/Session2Link.java
diff --git a/media/java/android/media/Session2Token.aidl b/media/apex/java/android/media/Session2Token.aidl
similarity index 100%
rename from media/java/android/media/Session2Token.aidl
rename to media/apex/java/android/media/Session2Token.aidl
diff --git a/media/java/android/media/Session2Token.java b/media/apex/java/android/media/Session2Token.java
similarity index 88%
rename from media/java/android/media/Session2Token.java
rename to media/apex/java/android/media/Session2Token.java
index d8f74c5..238cc2b 100644
--- a/media/java/android/media/Session2Token.java
+++ b/media/apex/java/android/media/Session2Token.java
@@ -35,7 +35,7 @@
import java.util.Objects;
/**
- * Represents an ongoing {@link MediaSession2} or a MediaSession2Service.
+ * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}.
* If it's representing a session service, it may not be ongoing.
* <p>
* This API is not generally intended for third party application developers.
@@ -48,14 +48,6 @@
* <p>
* It can be also obtained by {@link android.media.session.MediaSessionManager}.
*/
-// New version of MediaSession2.Token for following reasons
-// - Stop implementing Parcelable for updatable support
-// - Represent session and library service (formerly browser service) in one class.
-// Previously MediaSession2.Token was for session and ComponentName was for service.
-// This helps controller apps to keep target of dispatching media key events in uniform way.
-// For details about the reason, see following. (Android O+)
-// android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged
-// TODO: use @link for MediaSession2Service
public final class Session2Token implements Parcelable {
private static final String TAG = "Session2Token";
@@ -85,12 +77,13 @@
public static final int TYPE_SESSION = 0;
/**
- * Type for MediaSession2Service.
+ * Type for {@link MediaSession2Service}.
*/
public static final int TYPE_SESSION_SERVICE = 1;
private final int mUid;
- private final @TokenType int mType;
+ @TokenType
+ private final int mType;
private final String mPackageName;
private final String mServiceName;
private final Session2Link mSessionLink;
@@ -206,14 +199,6 @@
}
/**
- * @hide
- * @return component name of the session. Can be {@code null} for {@link #TYPE_SESSION}.
- */
- public ComponentName getComponentName() {
- return mComponentName;
- }
-
- /**
* @return type of the token
* @see #TYPE_SESSION
* @see #TYPE_SESSION_SERVICE
@@ -222,10 +207,7 @@
return mType;
}
- /**
- * @hide
- */
- public Session2Link getSessionLink() {
+ Session2Link getSessionLink() {
return mSessionLink;
}
diff --git a/media/java/android/media/VolumeProvider.java b/media/apex/java/android/media/VolumeProvider.java
similarity index 96%
rename from media/java/android/media/VolumeProvider.java
rename to media/apex/java/android/media/VolumeProvider.java
index 1c017c5..49202ee 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/apex/java/android/media/VolumeProvider.java
@@ -16,6 +16,7 @@
package android.media;
import android.annotation.IntDef;
+import android.annotation.SystemApi;
import android.media.session.MediaSession;
import java.lang.annotation.Retention;
@@ -147,6 +148,7 @@
* Sets a callback to receive volume changes.
* @hide
*/
+ @SystemApi
public void setCallback(Callback callback) {
mCallback = callback;
}
@@ -155,7 +157,11 @@
* Listens for changes to the volume.
* @hide
*/
- public static abstract class Callback {
+ @SystemApi
+ public abstract static class Callback {
+ /**
+ * Called when volume changed.
+ */
public abstract void onVolumeChanged(VolumeProvider volumeProvider);
}
}
diff --git a/media/java/android/media/browse/MediaBrowser.aidl b/media/apex/java/android/media/browse/MediaBrowser.aidl
similarity index 100%
rename from media/java/android/media/browse/MediaBrowser.aidl
rename to media/apex/java/android/media/browse/MediaBrowser.aidl
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/apex/java/android/media/browse/MediaBrowser.java
similarity index 98%
rename from media/java/android/media/browse/MediaBrowser.java
rename to media/apex/java/android/media/browse/MediaBrowser.java
index b1b14c6..2dffef9 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/apex/java/android/media/browse/MediaBrowser.java
@@ -284,8 +284,8 @@
*/
public @NonNull ComponentName getServiceComponent() {
if (!isConnected()) {
- throw new IllegalStateException("getServiceComponent() called while not connected" +
- " (state=" + mState + ")");
+ throw new IllegalStateException("getServiceComponent() called while not connected"
+ + " (state=" + mState + ")");
}
return mServiceComponent;
}
@@ -331,7 +331,7 @@
*
* @throws IllegalStateException if not connected.
*/
- public @NonNull MediaSession.Token getSessionToken() {
+ public @NonNull MediaSession.Token getSessionToken() {
if (!isConnected()) {
throw new IllegalStateException("getSessionToken() called while not connected (state="
+ mState + ")");
@@ -464,7 +464,7 @@
cb.onError(mediaId);
return;
}
- cb.onItemLoaded((MediaItem)item);
+ cb.onItemLoaded((MediaItem) item);
}
};
try {
@@ -575,7 +575,7 @@
}
}
- private final void onServiceConnected(final IMediaBrowserServiceCallbacks callback,
+ private void onServiceConnected(final IMediaBrowserServiceCallbacks callback,
final String root, final MediaSession.Token session, final Bundle extra) {
mHandler.post(new Runnable() {
@Override
@@ -625,7 +625,7 @@
});
}
- private final void onConnectionFailed(final IMediaBrowserServiceCallbacks callback) {
+ private void onConnectionFailed(final IMediaBrowserServiceCallbacks callback) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -652,7 +652,7 @@
});
}
- private final void onLoadChildren(final IMediaBrowserServiceCallbacks callback,
+ private void onLoadChildren(final IMediaBrowserServiceCallbacks callback,
final String parentId, final MediaParceledListSlice list, final Bundle options) {
mHandler.post(new Runnable() {
@Override
@@ -745,7 +745,7 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
+ @IntDef(flag = true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
public @interface Flags { }
/**
@@ -886,7 +886,7 @@
/**
* Callbacks for subscription related events.
*/
- public static abstract class SubscriptionCallback {
+ public abstract static class SubscriptionCallback {
Binder mToken;
public SubscriptionCallback() {
@@ -947,7 +947,7 @@
/**
* Callback for receiving the result of {@link #getItem}.
*/
- public static abstract class ItemCallback {
+ public abstract static class ItemCallback {
/**
* Called when the item has been returned by the connected service.
*
@@ -1078,7 +1078,7 @@
private static class ServiceCallbacks extends IMediaBrowserServiceCallbacks.Stub {
private WeakReference<MediaBrowser> mMediaBrowser;
- public ServiceCallbacks(MediaBrowser mediaBrowser) {
+ ServiceCallbacks(MediaBrowser mediaBrowser) {
mMediaBrowser = new WeakReference<MediaBrowser>(mediaBrowser);
}
@@ -1125,7 +1125,7 @@
private final List<SubscriptionCallback> mCallbacks;
private final List<Bundle> mOptionsList;
- public Subscription() {
+ Subscription() {
mCallbacks = new ArrayList<>();
mOptionsList = new ArrayList<>();
}
diff --git a/media/java/android/media/browse/MediaBrowserUtils.java b/media/apex/java/android/media/browse/MediaBrowserUtils.java
similarity index 94%
rename from media/java/android/media/browse/MediaBrowserUtils.java
rename to media/apex/java/android/media/browse/MediaBrowserUtils.java
index 2943e60..19d9f00 100644
--- a/media/java/android/media/browse/MediaBrowserUtils.java
+++ b/media/apex/java/android/media/browse/MediaBrowserUtils.java
@@ -22,6 +22,9 @@
* @hide
*/
public class MediaBrowserUtils {
+ /**
+ * Compares whether two bundles are the same.
+ */
public static boolean areSameOptions(Bundle options1, Bundle options2) {
if (options1 == options2) {
return true;
@@ -39,6 +42,9 @@
}
}
+ /**
+ * Returnes true if the page options has duplicated items.
+ */
public static boolean hasDuplicatedItems(Bundle options1, Bundle options2) {
int page1 = options1 == null ? -1 : options1.getInt(MediaBrowser.EXTRA_PAGE, -1);
int page2 = options2 == null ? -1 : options2.getInt(MediaBrowser.EXTRA_PAGE, -1);
diff --git a/media/java/android/media/session/ControllerCallbackLink.aidl b/media/apex/java/android/media/session/ControllerCallbackLink.aidl
similarity index 100%
rename from media/java/android/media/session/ControllerCallbackLink.aidl
rename to media/apex/java/android/media/session/ControllerCallbackLink.aidl
diff --git a/media/java/android/media/session/ControllerCallbackLink.java b/media/apex/java/android/media/session/ControllerCallbackLink.java
similarity index 93%
rename from media/java/android/media/session/ControllerCallbackLink.java
rename to media/apex/java/android/media/session/ControllerCallbackLink.java
index 95e19d2..adc14a5 100644
--- a/media/java/android/media/session/ControllerCallbackLink.java
+++ b/media/apex/java/android/media/session/ControllerCallbackLink.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession.QueueItem;
import android.os.Binder;
@@ -127,7 +128,8 @@
@RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyQueueChanged(@Nullable List<QueueItem> queue) {
try {
- mIControllerCallback.notifyQueueChanged(queue);
+ mIControllerCallback.notifyQueueChanged(queue == null ? null :
+ new MediaParceledListSlice(queue));
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -254,7 +256,7 @@
@Override
public void notifyPlaybackStateChanged(PlaybackState state) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPlaybackStateChanged(state);
@@ -265,7 +267,7 @@
@Override
public void notifyMetadataChanged(MediaMetadata metadata) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onMetadataChanged(metadata);
@@ -275,11 +277,11 @@
}
@Override
- public void notifyQueueChanged(List<QueueItem> queue) {
- ensureMediasControlPermission();
+ public void notifyQueueChanged(MediaParceledListSlice queue) {
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
- mCallbackStub.onQueueChanged(queue);
+ mCallbackStub.onQueueChanged(queue == null ? null : queue.getList());
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -287,7 +289,7 @@
@Override
public void notifyQueueTitleChanged(CharSequence title) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onQueueTitleChanged(title);
@@ -303,7 +305,7 @@
@Override
public void notifyVolumeInfoChanged(PlaybackInfo info) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onVolumeInfoChanged(info);
@@ -312,13 +314,7 @@
}
}
- private void ensureMediasControlPermission() {
- // Allow API calls from the System UI
- if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
- == PackageManager.PERMISSION_GRANTED) {
- return;
- }
-
+ private void ensureMediaControlPermission() {
// Check if it's system server or has MEDIA_CONTENT_CONTROL.
// Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
// check here.
diff --git a/media/java/android/media/session/ControllerLink.aidl b/media/apex/java/android/media/session/ControllerLink.aidl
similarity index 100%
rename from media/java/android/media/session/ControllerLink.aidl
rename to media/apex/java/android/media/session/ControllerLink.aidl
diff --git a/media/java/android/media/session/ControllerLink.java b/media/apex/java/android/media/session/ControllerLink.java
similarity index 100%
rename from media/java/android/media/session/ControllerLink.java
rename to media/apex/java/android/media/session/ControllerLink.java
diff --git a/media/java/android/media/session/ISession.aidl b/media/apex/java/android/media/session/ISession.aidl
similarity index 100%
rename from media/java/android/media/session/ISession.aidl
rename to media/apex/java/android/media/session/ISession.aidl
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/apex/java/android/media/session/ISessionCallback.aidl
similarity index 100%
rename from media/java/android/media/session/ISessionCallback.aidl
rename to media/apex/java/android/media/session/ISessionCallback.aidl
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/apex/java/android/media/session/ISessionController.aidl
similarity index 100%
rename from media/java/android/media/session/ISessionController.aidl
rename to media/apex/java/android/media/session/ISessionController.aidl
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/apex/java/android/media/session/ISessionControllerCallback.aidl
similarity index 91%
rename from media/java/android/media/session/ISessionControllerCallback.aidl
rename to media/apex/java/android/media/session/ISessionControllerCallback.aidl
index 5c02e7c..56ae852 100644
--- a/media/java/android/media/session/ISessionControllerCallback.aidl
+++ b/media/apex/java/android/media/session/ISessionControllerCallback.aidl
@@ -16,8 +16,8 @@
package android.media.session;
import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
import android.media.session.MediaController;
-import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
@@ -31,7 +31,7 @@
// These callbacks are for the TransportController
void notifyPlaybackStateChanged(in PlaybackState state);
void notifyMetadataChanged(in MediaMetadata metadata);
- void notifyQueueChanged(in List<MediaSession.QueueItem> queue);
+ void notifyQueueChanged(in MediaParceledListSlice queue);
void notifyQueueTitleChanged(CharSequence title);
void notifyExtrasChanged(in Bundle extras);
void notifyVolumeInfoChanged(in MediaController.PlaybackInfo info);
diff --git a/media/java/android/media/session/MediaController.aidl b/media/apex/java/android/media/session/MediaController.aidl
similarity index 100%
rename from media/java/android/media/session/MediaController.aidl
rename to media/apex/java/android/media/session/MediaController.aidl
diff --git a/media/java/android/media/session/MediaController.java b/media/apex/java/android/media/session/MediaController.java
similarity index 98%
rename from media/java/android/media/session/MediaController.java
rename to media/apex/java/android/media/session/MediaController.java
index 9d537c8..d43acf4 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/apex/java/android/media/session/MediaController.java
@@ -117,7 +117,7 @@
* @param token The token for the session.
*/
public MediaController(@NonNull Context context, @NonNull MediaSession.Token token) {
- this(context, token.getBinder());
+ this(context, token.getControllerLink());
}
/**
@@ -206,6 +206,7 @@
} catch (RuntimeException e) {
Log.wtf(TAG, "Error calling adjustVolumeBy", e);
}
+ break;
}
case KeyEvent.ACTION_UP: {
@@ -319,7 +320,7 @@
*
* @return The current set of flags for the session.
*/
- public @MediaSession.SessionFlags long getFlags() {
+ public long getFlags() {
try {
return mSessionBinder.getFlags();
} catch (RuntimeException e) {
@@ -582,7 +583,7 @@
return null;
}
- private final void postMessage(int what, Object obj, Bundle data) {
+ private void postMessage(int what, Object obj, Bundle data) {
synchronized (mLock) {
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
mCallbacks.get(i).post(what, obj, data);
@@ -594,7 +595,7 @@
* Callback for receiving updates from the session. A Callback can be
* registered using {@link #registerCallback}.
*/
- public static abstract class Callback {
+ public abstract static class Callback {
/**
* Override to handle the session being destroyed. The session is no
* longer valid after this call and calls to it will be ignored.
@@ -1191,11 +1192,11 @@
}
}
- private final static class MessageHandler extends Handler {
+ private static final class MessageHandler extends Handler {
private final MediaController.Callback mCallback;
private boolean mRegistered = false;
- public MessageHandler(Looper looper, MediaController.Callback cb) {
+ MessageHandler(Looper looper, MediaController.Callback cb) {
super(looper);
mCallback = cb;
}
diff --git a/media/apex/java/android/media/session/MediaSessionEngine.java b/media/apex/java/android/media/session/MediaSessionEngine.java
new file mode 100644
index 0000000..1f5fa5f
--- /dev/null
+++ b/media/apex/java/android/media/session/MediaSessionEngine.java
@@ -0,0 +1,1479 @@
+/*
+ * 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.session;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.UnsupportedAppUsage;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioAttributes;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.Rating;
+import android.media.VolumeProvider;
+import android.media.session.MediaSessionManager.RemoteUserInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.service.media.MediaBrowserService;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.view.KeyEvent;
+import android.view.ViewConfiguration;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class MediaSessionEngine implements AutoCloseable {
+ private static final String TAG = "MediaSession";
+
+ private final Object mLock = new Object();
+ private final int mMaxBitmapSize;
+
+ private final MediaSession.Token mSessionToken;
+ private final MediaController mController;
+ private final SessionLink mSessionLink;
+ private final SessionCallbackLink mCbLink;
+
+ // Do not change the name of mCallbackWrapper. Support lib accesses this by using reflection.
+ @UnsupportedAppUsage
+ private CallbackMessageHandler mCallbackHandler;
+ private VolumeProvider mVolumeProvider;
+ private PlaybackState mPlaybackState;
+
+ private boolean mActive = false;
+
+ /**
+ * Creates a new session. The session will automatically be registered with
+ * the system but will not be published until {@link #setActive(boolean)
+ * setActive(true)} is called. You must call {@link #close()} when
+ * finished with the session.
+ *
+ * @param context The context to use to create the session.
+ * @param sessionLink A session link for the binder of MediaSessionRecord
+ * @param cbStub A callback link that handles incoming command to {@link MediaSession.Callback}.
+ */
+ public MediaSessionEngine(@NonNull Context context, @NonNull SessionLink sessionLink,
+ @NonNull SessionCallbackLink cbLink, @NonNull CallbackStub cbStub, int maxBitmapSize) {
+ mSessionLink = sessionLink;
+ mCbLink = cbLink;
+ mMaxBitmapSize = maxBitmapSize;
+
+ cbStub.setSessionImpl(this);
+ mSessionToken = new MediaSession.Token(mSessionLink.getController());
+ mController = new MediaController(context, mSessionToken);
+ }
+
+ /**
+ * Set the callback to receive updates for the MediaSession. This includes
+ * media button events and transport controls. The caller's thread will be
+ * used to post updates.
+ * <p>
+ * Set the callback to null to stop receiving updates.
+ *
+ * @param callback The callback object
+ */
+ public void setCallback(@Nullable MediaSession.Callback callback) {
+ setCallback(callback, new Handler());
+ }
+
+ /**
+ * Set the callback to receive updates for the MediaSession. This includes
+ * media button events and transport controls.
+ * <p>
+ * Set the callback to null to stop receiving updates.
+ *
+ * @param callback The callback to receive updates on.
+ * @param handler The handler that events should be posted on.
+ */
+ public void setCallback(@Nullable MediaSession.Callback callback, @NonNull Handler handler) {
+ setCallbackInternal(callback == null ? null : new CallbackWrapper(callback), handler);
+ }
+
+ private void setCallbackInternal(CallbackWrapper callback, Handler handler) {
+ synchronized (mLock) {
+ if (mCallbackHandler != null) {
+ // We're updating the callback, clear the session from the old one.
+ mCallbackHandler.mCallbackWrapper.mSessionImpl = null;
+ mCallbackHandler.removeCallbacksAndMessages(null);
+ }
+ if (callback == null) {
+ mCallbackHandler = null;
+ return;
+ }
+ callback.mSessionImpl = this;
+ CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
+ callback);
+ mCallbackHandler = msgHandler;
+ }
+ }
+
+ /**
+ * Set an intent for launching UI for this Session. This can be used as a
+ * quick link to an ongoing media screen. The intent should be for an
+ * activity that may be started using {@link Activity#startActivity(Intent)}.
+ *
+ * @param pi The intent to launch to show UI for this Session.
+ */
+ public void setSessionActivity(@Nullable PendingIntent pi) {
+ try {
+ mSessionLink.setLaunchPendingIntent(pi);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e);
+ }
+ }
+
+ /**
+ * Set a pending intent for your media button receiver to allow restarting
+ * playback after the session has been stopped. If your app is started in
+ * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
+ * the pending intent.
+ *
+ * @param mbr The {@link PendingIntent} to send the media button event to.
+ */
+ public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
+ try {
+ mSessionLink.setMediaButtonReceiver(mbr);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
+ }
+ }
+
+ /**
+ * Set any flags for the session.
+ *
+ * @param flags The flags to set for this session.
+ */
+ public void setFlags(int flags) {
+ try {
+ mSessionLink.setFlags(flags);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setFlags.", e);
+ }
+ }
+
+ /**
+ * Set the attributes for this session's audio. This will affect the
+ * system's volume handling for this session. If
+ * {@link #setPlaybackToRemote} was previously called it will stop receiving
+ * volume commands and the system will begin sending volume changes to the
+ * appropriate stream.
+ * <p>
+ * By default sessions use attributes for media.
+ *
+ * @param attributes The {@link AudioAttributes} for this session's audio.
+ */
+ public void setPlaybackToLocal(AudioAttributes attributes) {
+ if (attributes == null) {
+ throw new IllegalArgumentException("Attributes cannot be null for local playback.");
+ }
+ try {
+ mSessionLink.setPlaybackToLocal(attributes);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
+ }
+ }
+
+ /**
+ * Configure this session to use remote volume handling. This must be called
+ * to receive volume button events, otherwise the system will adjust the
+ * appropriate stream volume for this session. If
+ * {@link #setPlaybackToLocal} was previously called the system will stop
+ * handling volume changes for this session and pass them to the volume
+ * provider instead.
+ *
+ * @param volumeProvider The provider that will handle volume changes. May
+ * not be null.
+ */
+ public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) {
+ if (volumeProvider == null) {
+ throw new IllegalArgumentException("volumeProvider may not be null!");
+ }
+ synchronized (mLock) {
+ mVolumeProvider = volumeProvider;
+ }
+ volumeProvider.setCallback(new VolumeProvider.Callback() {
+ @Override
+ public void onVolumeChanged(VolumeProvider volumeProvider) {
+ notifyRemoteVolumeChanged(volumeProvider);
+ }
+ });
+
+ try {
+ mSessionLink.setPlaybackToRemote(volumeProvider.getVolumeControl(),
+ volumeProvider.getMaxVolume());
+ mSessionLink.setCurrentVolume(volumeProvider.getCurrentVolume());
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
+ }
+ }
+
+ /**
+ * Set if this session is currently active and ready to receive commands. If
+ * set to false your session's controller may not be discoverable. You must
+ * set the session to active before it can start receiving media button
+ * events or transport commands.
+ *
+ * @param active Whether this session is active or not.
+ */
+ public void setActive(boolean active) {
+ if (mActive == active) {
+ return;
+ }
+ try {
+ mSessionLink.setActive(active);
+ mActive = active;
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setActive.", e);
+ }
+ }
+
+ /**
+ * Get the current active state of this session.
+ *
+ * @return True if the session is active, false otherwise.
+ */
+ public boolean isActive() {
+ return mActive;
+ }
+
+ /**
+ * Send a proprietary event to all MediaControllers listening to this
+ * Session. It's up to the Controller/Session owner to determine the meaning
+ * of any events.
+ *
+ * @param event The name of the event to send
+ * @param extras Any extras included with the event
+ */
+ public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) {
+ if (TextUtils.isEmpty(event)) {
+ throw new IllegalArgumentException("event cannot be null or empty");
+ }
+ try {
+ mSessionLink.sendEvent(event, extras);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Error sending event", e);
+ }
+ }
+
+ /**
+ * This must be called when an app has finished performing playback. If
+ * playback is expected to start again shortly the session can be left open,
+ * but it must be released if your activity or service is being destroyed.
+ */
+ public void close() {
+ try {
+ mSessionLink.destroySession();
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Error releasing session: ", e);
+ }
+ }
+
+ /**
+ * Retrieve a token object that can be used by apps to create a
+ * {@link MediaController} for interacting with this session. The owner of
+ * the session is responsible for deciding how to distribute these tokens.
+ *
+ * @return A token that can be used to create a MediaController for this
+ * session
+ */
+ public @NonNull MediaSession.Token getSessionToken() {
+ return mSessionToken;
+ }
+
+ /**
+ * Get a controller for this session. This is a convenience method to avoid
+ * having to cache your own controller in process.
+ *
+ * @return A controller for this session.
+ */
+ public @NonNull MediaController getController() {
+ return mController;
+ }
+
+ /**
+ * Update the current playback state.
+ *
+ * @param state The current state of playback
+ */
+ public void setPlaybackState(@Nullable PlaybackState state) {
+ mPlaybackState = state;
+ try {
+ mSessionLink.setPlaybackState(state);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Dead object in setPlaybackState.", e);
+ }
+ }
+
+ /**
+ * Update the current metadata. New metadata can be created using
+ * {@link android.media.MediaMetadata.Builder}. This operation may take time proportional to
+ * the size of the bitmap to replace large bitmaps with a scaled down copy.
+ *
+ * @param metadata The new metadata
+ * @see android.media.MediaMetadata.Builder#putBitmap
+ */
+ public void setMetadata(@Nullable MediaMetadata metadata) {
+ long duration = -1;
+ int fields = 0;
+ MediaDescription description = null;
+ if (metadata != null) {
+ metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
+ duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
+ }
+ fields = metadata.size();
+ description = metadata.getDescription();
+ }
+ String metadataDescription = "size=" + fields + ", description=" + description;
+
+ try {
+ mSessionLink.setMetadata(metadata, duration, metadataDescription);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Dead object in setPlaybackState.", e);
+ }
+ }
+
+ /**
+ * Update the list of items in the play queue. It is an ordered list and
+ * should contain the current item, and previous or upcoming items if they
+ * exist. Specify null if there is no current play queue.
+ * <p>
+ * The queue should be of reasonable size. If the play queue is unbounded
+ * within your app, it is better to send a reasonable amount in a sliding
+ * window instead.
+ *
+ * @param queue A list of items in the play queue.
+ */
+ public void setQueue(@Nullable List<MediaSession.QueueItem> queue) {
+ try {
+ mSessionLink.setQueue(queue);
+ } catch (RuntimeException e) {
+ Log.wtf("Dead object in setQueue.", e);
+ }
+ }
+
+ /**
+ * Set the title of the play queue. The UI should display this title along
+ * with the play queue itself.
+ * e.g. "Play Queue", "Now Playing", or an album name.
+ *
+ * @param title The title of the play queue.
+ */
+ public void setQueueTitle(@Nullable CharSequence title) {
+ try {
+ mSessionLink.setQueueTitle(title);
+ } catch (RuntimeException e) {
+ Log.wtf("Dead object in setQueueTitle.", e);
+ }
+ }
+
+ /**
+ * Set the style of rating used by this session. Apps trying to set the
+ * rating should use this style. Must be one of the following:
+ * <ul>
+ * <li>{@link Rating#RATING_NONE}</li>
+ * <li>{@link Rating#RATING_3_STARS}</li>
+ * <li>{@link Rating#RATING_4_STARS}</li>
+ * <li>{@link Rating#RATING_5_STARS}</li>
+ * <li>{@link Rating#RATING_HEART}</li>
+ * <li>{@link Rating#RATING_PERCENTAGE}</li>
+ * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li>
+ * </ul>
+ */
+ public void setRatingType(int type) {
+ try {
+ mSessionLink.setRatingType(type);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error in setRatingType.", e);
+ }
+ }
+
+ /**
+ * Set some extras that can be associated with the {@link MediaSession}. No assumptions should
+ * be made as to how a {@link MediaController} will handle these extras.
+ * Keys should be fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
+ *
+ * @param extras The extras associated with the {@link MediaSession}.
+ */
+ public void setExtras(@Nullable Bundle extras) {
+ try {
+ mSessionLink.setExtras(extras);
+ } catch (RuntimeException e) {
+ Log.wtf("Dead object in setExtras.", e);
+ }
+ }
+
+ /**
+ * Gets the controller information who sent the current request.
+ * <p>
+ * Note: This is only valid while in a request callback, such as
+ * {@link MediaSession.Callback#onPlay}.
+ *
+ * @throws IllegalStateException If this method is called outside of
+ * {@link MediaSession.Callback} methods.
+ * @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo)
+ */
+ public @NonNull RemoteUserInfo getCurrentControllerInfo() {
+ if (mCallbackHandler == null || mCallbackHandler.mCurrentControllerInfo == null) {
+ throw new IllegalStateException(
+ "This should be called inside of MediaSession.Callback methods");
+ }
+ return mCallbackHandler.mCurrentControllerInfo;
+ }
+
+ /**
+ * Notify the system that the remote volume changed.
+ *
+ * @param provider The provider that is handling volume changes.
+ * @hide
+ */
+ public void notifyRemoteVolumeChanged(VolumeProvider provider) {
+ synchronized (mLock) {
+ if (provider == null || provider != mVolumeProvider) {
+ Log.w(TAG, "Received update from stale volume provider");
+ return;
+ }
+ }
+ try {
+ mSessionLink.setCurrentVolume(provider.getCurrentVolume());
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error in notifyVolumeChanged", e);
+ }
+ }
+
+ /**
+ * Returns the name of the package that sent the last media button, transport control, or
+ * command from controllers and the system. This is only valid while in a request callback, such
+ * as {@link MediaSession.Callback#onPlay}.
+ */
+ public String getCallingPackage() {
+ if (mCallbackHandler != null && mCallbackHandler.mCurrentControllerInfo != null) {
+ return mCallbackHandler.mCurrentControllerInfo.getPackageName();
+ }
+ return null;
+ }
+
+ private void dispatchPrepare(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PREPARE, null, null);
+ }
+
+ private void dispatchPrepareFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
+ }
+
+ private void dispatchPrepareFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras);
+ }
+
+ private void dispatchPrepareFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_URI, uri, extras);
+ }
+
+ private void dispatchPlay(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PLAY, null, null);
+ }
+
+ private void dispatchPlayFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
+ }
+
+ private void dispatchPlayFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
+ }
+
+ private void dispatchPlayFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
+ }
+
+ private void dispatchSkipToItem(RemoteUserInfo caller, long id) {
+ postToCallback(caller, CallbackMessageHandler.MSG_SKIP_TO_ITEM, id, null);
+ }
+
+ private void dispatchPause(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PAUSE, null, null);
+ }
+
+ private void dispatchStop(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_STOP, null, null);
+ }
+
+ private void dispatchNext(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_NEXT, null, null);
+ }
+
+ private void dispatchPrevious(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PREVIOUS, null, null);
+ }
+
+ private void dispatchFastForward(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_FAST_FORWARD, null, null);
+ }
+
+ private void dispatchRewind(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_REWIND, null, null);
+ }
+
+ private void dispatchSeekTo(RemoteUserInfo caller, long pos) {
+ postToCallback(caller, CallbackMessageHandler.MSG_SEEK_TO, pos, null);
+ }
+
+ private void dispatchRate(RemoteUserInfo caller, Rating rating) {
+ postToCallback(caller, CallbackMessageHandler.MSG_RATE, rating, null);
+ }
+
+ private void dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args) {
+ postToCallback(caller, CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
+ }
+
+ private void dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent) {
+ postToCallback(caller, CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, null);
+ }
+
+ private void dispatchMediaButtonDelayed(RemoteUserInfo info, Intent mediaButtonIntent,
+ long delay) {
+ postToCallbackDelayed(info, CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT,
+ mediaButtonIntent, null, delay);
+ }
+
+ private void dispatchAdjustVolume(RemoteUserInfo caller, int direction) {
+ postToCallback(caller, CallbackMessageHandler.MSG_ADJUST_VOLUME, direction, null);
+ }
+
+ private void dispatchSetVolumeTo(RemoteUserInfo caller, int volume) {
+ postToCallback(caller, CallbackMessageHandler.MSG_SET_VOLUME, volume, null);
+ }
+
+ private void dispatchCommand(RemoteUserInfo caller, String command, Bundle args,
+ ResultReceiver resultCb) {
+ Command cmd = new Command(command, args, resultCb);
+ postToCallback(caller, CallbackMessageHandler.MSG_COMMAND, cmd, null);
+ }
+
+ private void postToCallback(RemoteUserInfo caller, int what, Object obj, Bundle data) {
+ postToCallbackDelayed(caller, what, obj, data, 0);
+ }
+
+ private void postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data,
+ long delay) {
+ synchronized (mLock) {
+ if (mCallbackHandler != null) {
+ mCallbackHandler.post(caller, what, obj, data, delay);
+ }
+ }
+ }
+
+ /**
+ * Return true if this is considered an active playback state.
+ */
+ public static boolean isActiveState(int state) {
+ switch (state) {
+ case PlaybackState.STATE_FAST_FORWARDING:
+ case PlaybackState.STATE_REWINDING:
+ case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+ case PlaybackState.STATE_SKIPPING_TO_NEXT:
+ case PlaybackState.STATE_BUFFERING:
+ case PlaybackState.STATE_CONNECTING:
+ case PlaybackState.STATE_PLAYING:
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Interface for handling MediaButtoneEvent
+ */
+ public interface MediaButtonEventDelegate {
+ /**
+ * Called when a media button is pressed and this session has the
+ * highest priority or a controller sends a media button event to the
+ * session.
+ *
+ * @param mediaButtonIntent an intent containing the KeyEvent as an extra
+ * @return True if the event was handled, false otherwise.
+ */
+ boolean onMediaButtonIntent(Intent mediaButtonIntent);
+ }
+
+ /**
+ * Receives media buttons, transport controls, and commands from controllers
+ * and the system. A callback may be set using {@link #setCallback}.
+ * @hide
+ */
+ public static class CallbackWrapper implements MediaButtonEventDelegate {
+
+ private final MediaSession.Callback mCallback;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ MediaSessionEngine mSessionImpl;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ CallbackMessageHandler mHandler;
+ private boolean mMediaPlayPauseKeyPending;
+
+ public CallbackWrapper(MediaSession.Callback callback) {
+ mCallback = callback;
+ if (mCallback != null) {
+ mCallback.onSetMediaButtonEventDelegate(this);
+ }
+ }
+
+ /**
+ * Called when a controller has sent a command to this session.
+ * The owner of the session may handle custom commands but is not
+ * required to.
+ *
+ * @param command The command name.
+ * @param args Optional parameters for the command, may be null.
+ * @param cb A result receiver to which a result may be sent by the command, may be null.
+ */
+ public void onCommand(@NonNull String command, @Nullable Bundle args,
+ @Nullable ResultReceiver cb) {
+ if (mCallback != null) {
+ mCallback.onCommand(command, args, cb);
+ }
+ }
+
+ /**
+ * Called when a media button is pressed and this session has the
+ * highest priority or a controller sends a media button event to the
+ * session. The default behavior will call the relevant method if the
+ * action for it was set.
+ * <p>
+ * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
+ * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
+ *
+ * @param mediaButtonIntent an intent containing the KeyEvent as an
+ * extra
+ * @return True if the event was handled, false otherwise.
+ */
+ public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
+ return mCallback == null ? false : mCallback.onMediaButtonEvent(mediaButtonIntent);
+ }
+
+ private void handleMediaPlayPauseKeySingleTapIfPending() {
+ if (!mMediaPlayPauseKeyPending) {
+ return;
+ }
+ mMediaPlayPauseKeyPending = false;
+ mHandler.removeMessages(CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
+ PlaybackState state = mSessionImpl.mPlaybackState;
+ long validActions = state == null ? 0 : state.getActions();
+ boolean isPlaying = state != null
+ && state.getState() == PlaybackState.STATE_PLAYING;
+ boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
+ | PlaybackState.ACTION_PLAY)) != 0;
+ boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
+ | PlaybackState.ACTION_PAUSE)) != 0;
+ if (isPlaying && canPause) {
+ onPause();
+ } else if (!isPlaying && canPlay) {
+ onPlay();
+ }
+ }
+
+ /**
+ * Override to handle requests to prepare playback. During the preparation, a session should
+ * not hold audio focus in order to allow other sessions play seamlessly. The state of
+ * playback should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is
+ * done.
+ */
+ public void onPrepare() {
+ if (mCallback != null) {
+ mCallback.onPrepare();
+ }
+ }
+
+ /**
+ * Override to handle requests to prepare for playing a specific mediaId that was provided
+ * by your app's {@link MediaBrowserService}. During the preparation, a session should not
+ * hold audio focus in order to allow other sessions play seamlessly. The state of playback
+ * should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+ * The playback of the prepared content should start in the implementation of
+ * {@link #onPlay}. Override {@link #onPlayFromMediaId} to handle requests for starting
+ * playback without preparation.
+ */
+ public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPrepareFromMediaId(mediaId, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to prepare playback from a search query. An empty query
+ * indicates that the app may prepare any music. The implementation should attempt to make a
+ * smart choice about what to play. During the preparation, a session should not hold audio
+ * focus in order to allow other sessions play seamlessly. The state of playback should be
+ * updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done. The playback
+ * of the prepared content should start in the implementation of {@link #onPlay}. Override
+ * {@link #onPlayFromSearch} to handle requests for starting playback without preparation.
+ */
+ public void onPrepareFromSearch(String query, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPrepareFromSearch(query, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to prepare a specific media item represented by a URI.
+ * During the preparation, a session should not hold audio focus in order to allow
+ * other sessions play seamlessly. The state of playback should be updated to
+ * {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+ * The playback of the prepared content should start in the implementation of
+ * {@link #onPlay}. Override {@link #onPlayFromUri} to handle requests
+ * for starting playback without preparation.
+ */
+ public void onPrepareFromUri(Uri uri, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPrepareFromUri(uri, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to begin playback.
+ */
+ public void onPlay() {
+ if (mCallback != null) {
+ mCallback.onPlay();
+ }
+ }
+
+ /**
+ * Override to handle requests to begin playback from a search query. An
+ * empty query indicates that the app may play any music. The
+ * implementation should attempt to make a smart choice about what to
+ * play.
+ */
+ public void onPlayFromSearch(String query, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPlayFromSearch(query, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to play a specific mediaId that was
+ * provided by your app's {@link MediaBrowserService}.
+ */
+ public void onPlayFromMediaId(String mediaId, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPlayFromMediaId(mediaId, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to play a specific media item represented by a URI.
+ */
+ public void onPlayFromUri(Uri uri, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPlayFromUri(uri, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to play an item with a given id from the
+ * play queue.
+ */
+ public void onSkipToQueueItem(long id) {
+ if (mCallback != null) {
+ mCallback.onSkipToQueueItem(id);
+ }
+ }
+
+ /**
+ * Override to handle requests to pause playback.
+ */
+ public void onPause() {
+ if (mCallback != null) {
+ mCallback.onPause();
+ }
+ }
+
+ /**
+ * Override to handle requests to skip to the next media item.
+ */
+ public void onSkipToNext() {
+ if (mCallback != null) {
+ mCallback.onSkipToNext();
+ }
+ }
+
+ /**
+ * Override to handle requests to skip to the previous media item.
+ */
+ public void onSkipToPrevious() {
+ if (mCallback != null) {
+ mCallback.onSkipToPrevious();
+ }
+ }
+
+ /**
+ * Override to handle requests to fast forward.
+ */
+ public void onFastForward() {
+ if (mCallback != null) {
+ mCallback.onFastForward();
+ }
+ }
+
+ /**
+ * Override to handle requests to rewind.
+ */
+ public void onRewind() {
+ if (mCallback != null) {
+ mCallback.onRewind();
+ }
+ }
+
+ /**
+ * Override to handle requests to stop playback.
+ */
+ public void onStop() {
+ if (mCallback != null) {
+ mCallback.onStop();
+ }
+ }
+
+ /**
+ * Override to handle requests to seek to a specific position in ms.
+ *
+ * @param pos New position to move to, in milliseconds.
+ */
+ public void onSeekTo(long pos) {
+ if (mCallback != null) {
+ mCallback.onSeekTo(pos);
+ }
+ }
+
+ /**
+ * Override to handle the item being rated.
+ *
+ * @param rating
+ */
+ public void onSetRating(@NonNull Rating rating) {
+ if (mCallback != null) {
+ mCallback.onSetRating(rating);
+ }
+ }
+
+ /**
+ * Called when a {@link MediaController} wants a {@link PlaybackState.CustomAction} to be
+ * performed.
+ *
+ * @param action The action that was originally sent in the
+ * {@link PlaybackState.CustomAction}.
+ * @param extras Optional extras specified by the {@link MediaController}.
+ */
+ public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onCustomAction(action, extras);
+ }
+ }
+
+ @Override
+ public boolean onMediaButtonIntent(Intent mediaButtonIntent) {
+ if (mSessionImpl != null && mHandler != null
+ && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
+ KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+ if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
+ PlaybackState state = mSessionImpl.mPlaybackState;
+ long validActions = state == null ? 0 : state.getActions();
+ switch (ke.getKeyCode()) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ if (ke.getRepeatCount() > 0) {
+ // Consider long-press as a single tap.
+ handleMediaPlayPauseKeySingleTapIfPending();
+ } else if (mMediaPlayPauseKeyPending) {
+ // Consider double tap as the next.
+ mHandler.removeMessages(CallbackMessageHandler
+ .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
+ mMediaPlayPauseKeyPending = false;
+ if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
+ onSkipToNext();
+ }
+ } else {
+ mMediaPlayPauseKeyPending = true;
+ mSessionImpl.dispatchMediaButtonDelayed(
+ mSessionImpl.getCurrentControllerInfo(),
+ mediaButtonIntent, ViewConfiguration.getDoubleTapTimeout());
+ }
+ return true;
+ default:
+ // If another key is pressed within double tap timeout, consider the
+ // pending play/pause as a single tap to handle media keys in order.
+ handleMediaPlayPauseKeySingleTapIfPending();
+ break;
+ }
+
+ switch (ke.getKeyCode()) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
+ onPlay();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
+ onPause();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
+ onSkipToNext();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
+ onSkipToPrevious();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ if ((validActions & PlaybackState.ACTION_STOP) != 0) {
+ onStop();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
+ onFastForward();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
+ onRewind();
+ return true;
+ }
+ break;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public static final class CallbackStub extends SessionCallbackLink.CallbackStub {
+ private WeakReference<MediaSessionEngine> mSessionImpl;
+
+ private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid) {
+ return new RemoteUserInfo(packageName, pid, uid);
+ }
+
+ public CallbackStub() {
+ }
+
+ @Override
+ public void onCommand(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchCommand(createRemoteUserInfo(packageName, pid, uid),
+ command, args, cb);
+ }
+ }
+
+ @Override
+ public void onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent,
+ int sequenceNumber, ResultReceiver cb) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ try {
+ if (sessionImpl != null) {
+ sessionImpl.dispatchMediaButton(
+ createRemoteUserInfo(packageName, pid, uid), mediaButtonIntent);
+ }
+ } finally {
+ if (cb != null) {
+ cb.send(sequenceNumber, null);
+ }
+ }
+ }
+
+ @Override
+ public void onMediaButtonFromController(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, Intent mediaButtonIntent) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid),
+ mediaButtonIntent);
+ }
+ }
+
+ @Override
+ public void onPrepare(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onPrepareFromMediaId(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String mediaId,
+ Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPrepareFromMediaId(
+ createRemoteUserInfo(packageName, pid, uid), mediaId, extras);
+ }
+ }
+
+ @Override
+ public void onPrepareFromSearch(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String query,
+ Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPrepareFromSearch(
+ createRemoteUserInfo(packageName, pid, uid), query, extras);
+ }
+ }
+
+ @Override
+ public void onPrepareFromUri(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPrepareFromUri(
+ createRemoteUserInfo(packageName, pid, uid), uri, extras);
+ }
+ }
+
+ @Override
+ public void onPlay(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPlay(createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onPlayFromMediaId(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String mediaId,
+ Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPlayFromMediaId(
+ createRemoteUserInfo(packageName, pid, uid), mediaId, extras);
+ }
+ }
+
+ @Override
+ public void onPlayFromSearch(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String query,
+ Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPlayFromSearch(
+ createRemoteUserInfo(packageName, pid, uid), query, extras);
+ }
+ }
+
+ @Override
+ public void onPlayFromUri(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPlayFromUri(
+ createRemoteUserInfo(packageName, pid, uid), uri, extras);
+ }
+ }
+
+ @Override
+ public void onSkipToTrack(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, long id) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchSkipToItem(
+ createRemoteUserInfo(packageName, pid, uid), id);
+ }
+ }
+
+ @Override
+ public void onPause(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPause(createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onStop(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchStop(createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onNext(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchNext(createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onPrevious(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onFastForward(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchFastForward(
+ createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onRewind(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchRewind(createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onSeekTo(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, long pos) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchSeekTo(
+ createRemoteUserInfo(packageName, pid, uid), pos);
+ }
+ }
+
+ @Override
+ public void onRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
+ Rating rating) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchRate(
+ createRemoteUserInfo(packageName, pid, uid), rating);
+ }
+ }
+
+ @Override
+ public void onCustomAction(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String action, Bundle args) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchCustomAction(
+ createRemoteUserInfo(packageName, pid, uid), action, args);
+ }
+ }
+
+ @Override
+ public void onAdjustVolume(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, int direction) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchAdjustVolume(
+ createRemoteUserInfo(packageName, pid, uid), direction);
+ }
+ }
+
+ @Override
+ public void onSetVolumeTo(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, int value) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchSetVolumeTo(
+ createRemoteUserInfo(packageName, pid, uid), value);
+ }
+ }
+
+ void setSessionImpl(MediaSessionEngine sessionImpl) {
+ mSessionImpl = new WeakReference<>(sessionImpl);
+ }
+ }
+
+ /**
+ * A single item that is part of the play queue. It contains a description
+ * of the item and its id in the queue.
+ */
+ public static final class QueueItem {
+ /**
+ * This id is reserved. No items can be explicitly assigned this id.
+ */
+ public static final int UNKNOWN_ID = -1;
+
+ private final MediaDescription mDescription;
+ private final long mId;
+
+ /**
+ * Create a new {@link MediaSession.QueueItem}.
+ *
+ * @param description The {@link MediaDescription} for this item.
+ * @param id An identifier for this item. It must be unique within the
+ * play queue and cannot be {@link #UNKNOWN_ID}.
+ */
+ public QueueItem(MediaDescription description, long id) {
+ if (description == null) {
+ throw new IllegalArgumentException("Description cannot be null.");
+ }
+ if (id == UNKNOWN_ID) {
+ throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
+ }
+ mDescription = description;
+ mId = id;
+ }
+
+ public QueueItem(Parcel in) {
+ mDescription = MediaDescription.CREATOR.createFromParcel(in);
+ mId = in.readLong();
+ }
+
+ /**
+ * Get the description for this item.
+ */
+ public MediaDescription getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Get the queue id for this item.
+ */
+ public long getQueueId() {
+ return mId;
+ }
+
+ /**
+ * Flatten this object in to a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ mDescription.writeToParcel(dest, flags);
+ dest.writeLong(mId);
+ }
+
+ @Override
+ public String toString() {
+ return "MediaSession.QueueItem {" + "Description=" + mDescription + ", Id=" + mId
+ + " }";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null) {
+ return false;
+ }
+
+ if (!(o instanceof QueueItem)) {
+ return false;
+ }
+
+ final QueueItem item = (QueueItem) o;
+ if (mId != item.mId) {
+ return false;
+ }
+
+ if (!Objects.equals(mDescription, item.mDescription)) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ private static final class Command {
+ public final String command;
+ public final Bundle extras;
+ public final ResultReceiver stub;
+
+ Command(String command, Bundle extras, ResultReceiver stub) {
+ this.command = command;
+ this.extras = extras;
+ this.stub = stub;
+ }
+ }
+
+ private class CallbackMessageHandler extends Handler {
+ private static final int MSG_COMMAND = 1;
+ private static final int MSG_MEDIA_BUTTON = 2;
+ private static final int MSG_PREPARE = 3;
+ private static final int MSG_PREPARE_MEDIA_ID = 4;
+ private static final int MSG_PREPARE_SEARCH = 5;
+ private static final int MSG_PREPARE_URI = 6;
+ private static final int MSG_PLAY = 7;
+ private static final int MSG_PLAY_MEDIA_ID = 8;
+ private static final int MSG_PLAY_SEARCH = 9;
+ private static final int MSG_PLAY_URI = 10;
+ private static final int MSG_SKIP_TO_ITEM = 11;
+ private static final int MSG_PAUSE = 12;
+ private static final int MSG_STOP = 13;
+ private static final int MSG_NEXT = 14;
+ private static final int MSG_PREVIOUS = 15;
+ private static final int MSG_FAST_FORWARD = 16;
+ private static final int MSG_REWIND = 17;
+ private static final int MSG_SEEK_TO = 18;
+ private static final int MSG_RATE = 19;
+ private static final int MSG_CUSTOM_ACTION = 20;
+ private static final int MSG_ADJUST_VOLUME = 21;
+ private static final int MSG_SET_VOLUME = 22;
+ private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 23;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ CallbackWrapper mCallbackWrapper;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ RemoteUserInfo mCurrentControllerInfo;
+
+ CallbackMessageHandler(Looper looper, CallbackWrapper callbackWrapper) {
+ super(looper);
+ mCallbackWrapper = callbackWrapper;
+ mCallbackWrapper.mHandler = this;
+ }
+
+ void post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs) {
+ Pair<RemoteUserInfo, Object> objWithCaller = Pair.create(caller, obj);
+ Message msg = obtainMessage(what, objWithCaller);
+ msg.setAsynchronous(true);
+ msg.setData(data);
+ if (delayMs > 0) {
+ sendMessageDelayed(msg, delayMs);
+ } else {
+ sendMessage(msg);
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ mCurrentControllerInfo = ((Pair<RemoteUserInfo, Object>) msg.obj).first;
+
+ VolumeProvider vp;
+ Object obj = ((Pair<RemoteUserInfo, Object>) msg.obj).second;
+
+ switch (msg.what) {
+ case MSG_COMMAND:
+ Command cmd = (Command) obj;
+ mCallbackWrapper.onCommand(cmd.command, cmd.extras, cmd.stub);
+ break;
+ case MSG_MEDIA_BUTTON:
+ mCallbackWrapper.onMediaButtonEvent((Intent) obj);
+ break;
+ case MSG_PREPARE:
+ mCallbackWrapper.onPrepare();
+ break;
+ case MSG_PREPARE_MEDIA_ID:
+ mCallbackWrapper.onPrepareFromMediaId((String) obj, msg.getData());
+ break;
+ case MSG_PREPARE_SEARCH:
+ mCallbackWrapper.onPrepareFromSearch((String) obj, msg.getData());
+ break;
+ case MSG_PREPARE_URI:
+ mCallbackWrapper.onPrepareFromUri((Uri) obj, msg.getData());
+ break;
+ case MSG_PLAY:
+ mCallbackWrapper.onPlay();
+ break;
+ case MSG_PLAY_MEDIA_ID:
+ mCallbackWrapper.onPlayFromMediaId((String) obj, msg.getData());
+ break;
+ case MSG_PLAY_SEARCH:
+ mCallbackWrapper.onPlayFromSearch((String) obj, msg.getData());
+ break;
+ case MSG_PLAY_URI:
+ mCallbackWrapper.onPlayFromUri((Uri) obj, msg.getData());
+ break;
+ case MSG_SKIP_TO_ITEM:
+ mCallbackWrapper.onSkipToQueueItem((Long) obj);
+ break;
+ case MSG_PAUSE:
+ mCallbackWrapper.onPause();
+ break;
+ case MSG_STOP:
+ mCallbackWrapper.onStop();
+ break;
+ case MSG_NEXT:
+ mCallbackWrapper.onSkipToNext();
+ break;
+ case MSG_PREVIOUS:
+ mCallbackWrapper.onSkipToPrevious();
+ break;
+ case MSG_FAST_FORWARD:
+ mCallbackWrapper.onFastForward();
+ break;
+ case MSG_REWIND:
+ mCallbackWrapper.onRewind();
+ break;
+ case MSG_SEEK_TO:
+ mCallbackWrapper.onSeekTo((Long) obj);
+ break;
+ case MSG_RATE:
+ mCallbackWrapper.onSetRating((Rating) obj);
+ break;
+ case MSG_CUSTOM_ACTION:
+ mCallbackWrapper.onCustomAction((String) obj, msg.getData());
+ break;
+ case MSG_ADJUST_VOLUME:
+ synchronized (mLock) {
+ vp = mVolumeProvider;
+ }
+ if (vp != null) {
+ vp.onAdjustVolume((int) obj);
+ }
+ break;
+ case MSG_SET_VOLUME:
+ synchronized (mLock) {
+ vp = mVolumeProvider;
+ }
+ if (vp != null) {
+ vp.onSetVolumeTo((int) obj);
+ }
+ break;
+ case MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT:
+ mCallbackWrapper.handleMediaPlayPauseKeySingleTapIfPending();
+ break;
+ }
+ mCurrentControllerInfo = null;
+ }
+ }
+}
diff --git a/media/java/android/media/session/MediaSessionProviderService.java b/media/apex/java/android/media/session/MediaSessionProviderService.java
similarity index 100%
rename from media/java/android/media/session/MediaSessionProviderService.java
rename to media/apex/java/android/media/session/MediaSessionProviderService.java
diff --git a/media/java/android/media/session/PlaybackState.aidl b/media/apex/java/android/media/session/PlaybackState.aidl
similarity index 100%
rename from media/java/android/media/session/PlaybackState.aidl
rename to media/apex/java/android/media/session/PlaybackState.aidl
diff --git a/media/java/android/media/session/PlaybackState.java b/media/apex/java/android/media/session/PlaybackState.java
similarity index 80%
rename from media/java/android/media/session/PlaybackState.java
rename to media/apex/java/android/media/session/PlaybackState.java
index 2c57d1f..6b28c97 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/apex/java/android/media/session/PlaybackState.java
@@ -19,7 +19,6 @@
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.Nullable;
-import android.media.RemoteControlClient;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -42,7 +41,7 @@
/**
* @hide
*/
- @LongDef(flag=true, value={ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
+ @LongDef(flag = true, value = {ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
ACTION_SKIP_TO_PREVIOUS, ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_SET_RATING,
ACTION_SEEK_TO, ACTION_PLAY_PAUSE, ACTION_PLAY_FROM_MEDIA_ID, ACTION_PLAY_FROM_SEARCH,
ACTION_SKIP_TO_QUEUE_ITEM, ACTION_PLAY_FROM_URI, ACTION_PREPARE,
@@ -192,42 +191,42 @@
* @see Builder#setState(int, long, float)
* @see Builder#setState(int, long, float, long)
*/
- public final static int STATE_NONE = 0;
+ public static final int STATE_NONE = 0;
/**
* State indicating this item is currently stopped.
*
* @see Builder#setState
*/
- public final static int STATE_STOPPED = 1;
+ public static final int STATE_STOPPED = 1;
/**
* State indicating this item is currently paused.
*
* @see Builder#setState
*/
- public final static int STATE_PAUSED = 2;
+ public static final int STATE_PAUSED = 2;
/**
* State indicating this item is currently playing.
*
* @see Builder#setState
*/
- public final static int STATE_PLAYING = 3;
+ public static final int STATE_PLAYING = 3;
/**
* State indicating this item is currently fast forwarding.
*
* @see Builder#setState
*/
- public final static int STATE_FAST_FORWARDING = 4;
+ public static final int STATE_FAST_FORWARDING = 4;
/**
* State indicating this item is currently rewinding.
*
* @see Builder#setState
*/
- public final static int STATE_REWINDING = 5;
+ public static final int STATE_REWINDING = 5;
/**
* State indicating this item is currently buffering and will begin playing
@@ -235,7 +234,7 @@
*
* @see Builder#setState
*/
- public final static int STATE_BUFFERING = 6;
+ public static final int STATE_BUFFERING = 6;
/**
* State indicating this item is currently in an error state. The error
@@ -243,7 +242,7 @@
*
* @see Builder#setState
*/
- public final static int STATE_ERROR = 7;
+ public static final int STATE_ERROR = 7;
/**
* State indicating the class doing playback is currently connecting to a
@@ -253,21 +252,21 @@
*
* @see Builder#setState
*/
- public final static int STATE_CONNECTING = 8;
+ public static final int STATE_CONNECTING = 8;
/**
* State indicating the player is currently skipping to the previous item.
*
* @see Builder#setState
*/
- public final static int STATE_SKIPPING_TO_PREVIOUS = 9;
+ public static final int STATE_SKIPPING_TO_PREVIOUS = 9;
/**
* State indicating the player is currently skipping to the next item.
*
* @see Builder#setState
*/
- public final static int STATE_SKIPPING_TO_NEXT = 10;
+ public static final int STATE_SKIPPING_TO_NEXT = 10;
/**
* State indicating the player is currently skipping to a specific item in
@@ -275,12 +274,12 @@
*
* @see Builder#setState
*/
- public final static int STATE_SKIPPING_TO_QUEUE_ITEM = 11;
+ public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11;
/**
* Use this value for the position to indicate the position is not known.
*/
- public final static long PLAYBACK_POSITION_UNKNOWN = -1;
+ public static final long PLAYBACK_POSITION_UNKNOWN = -1;
private final int mState;
private final long mPosition;
@@ -481,161 +480,6 @@
return mExtras;
}
- /**
- * Get the {@link PlaybackState} state for the given
- * {@link RemoteControlClient} state.
- *
- * @param rccState The state used by {@link RemoteControlClient}.
- * @return The equivalent state used by {@link PlaybackState}.
- * @hide
- */
- public static int getStateFromRccState(int rccState) {
- switch (rccState) {
- case RemoteControlClient.PLAYSTATE_BUFFERING:
- return STATE_BUFFERING;
- case RemoteControlClient.PLAYSTATE_ERROR:
- return STATE_ERROR;
- case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
- return STATE_FAST_FORWARDING;
- case RemoteControlClient.PLAYSTATE_NONE:
- return STATE_NONE;
- case RemoteControlClient.PLAYSTATE_PAUSED:
- return STATE_PAUSED;
- case RemoteControlClient.PLAYSTATE_PLAYING:
- return STATE_PLAYING;
- case RemoteControlClient.PLAYSTATE_REWINDING:
- return STATE_REWINDING;
- case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
- return STATE_SKIPPING_TO_PREVIOUS;
- case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
- return STATE_SKIPPING_TO_NEXT;
- case RemoteControlClient.PLAYSTATE_STOPPED:
- return STATE_STOPPED;
- default:
- return -1;
- }
- }
-
- /**
- * Get the {@link RemoteControlClient} state for the given
- * {@link PlaybackState} state.
- *
- * @param state The state used by {@link PlaybackState}.
- * @return The equivalent state used by {@link RemoteControlClient}.
- * @hide
- */
- public static int getRccStateFromState(int state) {
- switch (state) {
- case STATE_BUFFERING:
- return RemoteControlClient.PLAYSTATE_BUFFERING;
- case STATE_ERROR:
- return RemoteControlClient.PLAYSTATE_ERROR;
- case STATE_FAST_FORWARDING:
- return RemoteControlClient.PLAYSTATE_FAST_FORWARDING;
- case STATE_NONE:
- return RemoteControlClient.PLAYSTATE_NONE;
- case STATE_PAUSED:
- return RemoteControlClient.PLAYSTATE_PAUSED;
- case STATE_PLAYING:
- return RemoteControlClient.PLAYSTATE_PLAYING;
- case STATE_REWINDING:
- return RemoteControlClient.PLAYSTATE_REWINDING;
- case STATE_SKIPPING_TO_PREVIOUS:
- return RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS;
- case STATE_SKIPPING_TO_NEXT:
- return RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS;
- case STATE_STOPPED:
- return RemoteControlClient.PLAYSTATE_STOPPED;
- default:
- return -1;
- }
- }
-
- /**
- * @hide
- */
- public static long getActionsFromRccControlFlags(int rccFlags) {
- long actions = 0;
- long flag = 1;
- while (flag <= rccFlags) {
- if ((flag & rccFlags) != 0) {
- actions |= getActionForRccFlag((int) flag);
- }
- flag = flag << 1;
- }
- return actions;
- }
-
- /**
- * @hide
- */
- public static int getRccControlFlagsFromActions(long actions) {
- int rccFlags = 0;
- long action = 1;
- while (action <= actions && action < Integer.MAX_VALUE) {
- if ((action & actions) != 0) {
- rccFlags |= getRccFlagForAction(action);
- }
- action = action << 1;
- }
- return rccFlags;
- }
-
- private static long getActionForRccFlag(int flag) {
- switch (flag) {
- case RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS:
- return ACTION_SKIP_TO_PREVIOUS;
- case RemoteControlClient.FLAG_KEY_MEDIA_REWIND:
- return ACTION_REWIND;
- case RemoteControlClient.FLAG_KEY_MEDIA_PLAY:
- return ACTION_PLAY;
- case RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE:
- return ACTION_PLAY_PAUSE;
- case RemoteControlClient.FLAG_KEY_MEDIA_PAUSE:
- return ACTION_PAUSE;
- case RemoteControlClient.FLAG_KEY_MEDIA_STOP:
- return ACTION_STOP;
- case RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD:
- return ACTION_FAST_FORWARD;
- case RemoteControlClient.FLAG_KEY_MEDIA_NEXT:
- return ACTION_SKIP_TO_NEXT;
- case RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE:
- return ACTION_SEEK_TO;
- case RemoteControlClient.FLAG_KEY_MEDIA_RATING:
- return ACTION_SET_RATING;
- }
- return 0;
- }
-
- private static int getRccFlagForAction(long action) {
- // We only care about the lower set of actions that can map to rcc
- // flags.
- int testAction = action < Integer.MAX_VALUE ? (int) action : 0;
- switch (testAction) {
- case (int) ACTION_SKIP_TO_PREVIOUS:
- return RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS;
- case (int) ACTION_REWIND:
- return RemoteControlClient.FLAG_KEY_MEDIA_REWIND;
- case (int) ACTION_PLAY:
- return RemoteControlClient.FLAG_KEY_MEDIA_PLAY;
- case (int) ACTION_PLAY_PAUSE:
- return RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE;
- case (int) ACTION_PAUSE:
- return RemoteControlClient.FLAG_KEY_MEDIA_PAUSE;
- case (int) ACTION_STOP:
- return RemoteControlClient.FLAG_KEY_MEDIA_STOP;
- case (int) ACTION_FAST_FORWARD:
- return RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD;
- case (int) ACTION_SKIP_TO_NEXT:
- return RemoteControlClient.FLAG_KEY_MEDIA_NEXT;
- case (int) ACTION_SEEK_TO:
- return RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE;
- case (int) ACTION_SET_RATING:
- return RemoteControlClient.FLAG_KEY_MEDIA_RATING;
- }
- return 0;
- }
-
public static final Parcelable.Creator<PlaybackState> CREATOR =
new Parcelable.Creator<PlaybackState>() {
@Override
@@ -690,19 +534,19 @@
return 0;
}
- public static final Parcelable.Creator<PlaybackState.CustomAction> CREATOR
- = new Parcelable.Creator<PlaybackState.CustomAction>() {
+ public static final Parcelable.Creator<PlaybackState.CustomAction> CREATOR =
+ new Parcelable.Creator<PlaybackState.CustomAction>() {
- @Override
- public PlaybackState.CustomAction createFromParcel(Parcel p) {
- return new PlaybackState.CustomAction(p);
- }
+ @Override
+ public PlaybackState.CustomAction createFromParcel(Parcel p) {
+ return new PlaybackState.CustomAction(p);
+ }
- @Override
- public PlaybackState.CustomAction[] newArray(int size) {
- return new PlaybackState.CustomAction[size];
- }
- };
+ @Override
+ public PlaybackState.CustomAction[] newArray(int size) {
+ return new PlaybackState.CustomAction[size];
+ }
+ };
/**
* Returns the action of the {@link CustomAction}.
@@ -744,10 +588,7 @@
@Override
public String toString() {
- return "Action:" +
- "mName='" + mName +
- ", mIcon=" + mIcon +
- ", mExtras=" + mExtras;
+ return "Action:" + "mName='" + mName + ", mIcon=" + mIcon + ", mExtras=" + mExtras;
}
/**
diff --git a/media/java/android/media/session/SessionCallbackLink.aidl b/media/apex/java/android/media/session/SessionCallbackLink.aidl
similarity index 100%
rename from media/java/android/media/session/SessionCallbackLink.aidl
rename to media/apex/java/android/media/session/SessionCallbackLink.aidl
diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/apex/java/android/media/session/SessionCallbackLink.java
similarity index 96%
rename from media/java/android/media/session/SessionCallbackLink.java
rename to media/apex/java/android/media/session/SessionCallbackLink.java
index 0265687b..3bcb65c 100644
--- a/media/java/android/media/session/SessionCallbackLink.java
+++ b/media/apex/java/android/media/session/SessionCallbackLink.java
@@ -669,7 +669,7 @@
@Override
public void notifyCommand(String packageName, int pid, int uid,
ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onCommand(packageName, pid, uid, caller, command, args, cb);
@@ -681,7 +681,7 @@
@Override
public void notifyMediaButton(String packageName, int pid, int uid,
Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onMediaButton(packageName, pid, uid, mediaButtonIntent,
@@ -694,7 +694,7 @@
@Override
public void notifyMediaButtonFromController(String packageName, int pid, int uid,
ControllerCallbackLink caller, Intent mediaButtonIntent) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onMediaButtonFromController(packageName, pid, uid, caller,
@@ -707,7 +707,7 @@
@Override
public void notifyPrepare(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPrepare(packageName, pid, uid, caller);
@@ -719,7 +719,7 @@
@Override
public void notifyPrepareFromMediaId(String packageName, int pid, int uid,
ControllerCallbackLink caller, String mediaId, Bundle extras) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
@@ -731,7 +731,7 @@
@Override
public void notifyPrepareFromSearch(String packageName, int pid, int uid,
ControllerCallbackLink caller, String query, Bundle extras) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPrepareFromSearch(packageName, pid, uid, caller, query, extras);
@@ -743,7 +743,7 @@
@Override
public void notifyPrepareFromUri(String packageName, int pid, int uid,
ControllerCallbackLink caller, Uri uri, Bundle extras) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPrepareFromUri(packageName, pid, uid, caller, uri, extras);
@@ -755,7 +755,7 @@
@Override
public void notifyPlay(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPlay(packageName, pid, uid, caller);
@@ -767,7 +767,7 @@
@Override
public void notifyPlayFromMediaId(String packageName, int pid, int uid,
ControllerCallbackLink caller, String mediaId, Bundle extras) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
@@ -779,7 +779,7 @@
@Override
public void notifyPlayFromSearch(String packageName, int pid, int uid,
ControllerCallbackLink caller, String query, Bundle extras) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPlayFromSearch(packageName, pid, uid, caller, query, extras);
@@ -791,7 +791,7 @@
@Override
public void notifyPlayFromUri(String packageName, int pid, int uid,
ControllerCallbackLink caller, Uri uri, Bundle extras) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPlayFromUri(packageName, pid, uid, caller, uri, extras);
@@ -803,7 +803,7 @@
@Override
public void notifySkipToTrack(String packageName, int pid, int uid,
ControllerCallbackLink caller, long id) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onSkipToTrack(packageName, pid, uid, caller, id);
@@ -815,7 +815,7 @@
@Override
public void notifyPause(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPause(packageName, pid, uid, caller);
@@ -827,7 +827,7 @@
@Override
public void notifyStop(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onStop(packageName, pid, uid, caller);
@@ -839,7 +839,7 @@
@Override
public void notifyNext(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onNext(packageName, pid, uid, caller);
@@ -851,7 +851,7 @@
@Override
public void notifyPrevious(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPrevious(packageName, pid, uid, caller);
@@ -863,7 +863,7 @@
@Override
public void notifyFastForward(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onFastForward(packageName, pid, uid, caller);
@@ -875,7 +875,7 @@
@Override
public void notifyRewind(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onRewind(packageName, pid, uid, caller);
@@ -887,7 +887,7 @@
@Override
public void notifySeekTo(String packageName, int pid, int uid,
ControllerCallbackLink caller, long pos) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onSeekTo(packageName, pid, uid, caller, pos);
@@ -899,7 +899,7 @@
@Override
public void notifyRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
Rating rating) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onRate(packageName, pid, uid, caller, rating);
@@ -910,7 +910,7 @@
public void notifyCustomAction(String packageName, int pid, int uid,
ControllerCallbackLink caller, String action, Bundle args) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onCustomAction(packageName, pid, uid, caller, action, args);
@@ -922,7 +922,7 @@
@Override
public void notifyAdjustVolume(String packageName, int pid, int uid,
ControllerCallbackLink caller, int direction) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onAdjustVolume(packageName, pid, uid, caller, direction);
@@ -934,7 +934,7 @@
@Override
public void notifySetVolumeTo(String packageName, int pid, int uid,
ControllerCallbackLink caller, int value) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onSetVolumeTo(packageName, pid, uid, caller, value);
@@ -943,13 +943,7 @@
}
}
- private void ensureMediasControlPermission() {
- // Allow API calls from the System UI
- if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
- == PackageManager.PERMISSION_GRANTED) {
- return;
- }
-
+ private void ensureMediaControlPermission() {
// Check if it's system server or has MEDIA_CONTENT_CONTROL.
// Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
// check here.
diff --git a/media/java/android/media/session/SessionLink.aidl b/media/apex/java/android/media/session/SessionLink.aidl
similarity index 100%
rename from media/java/android/media/session/SessionLink.aidl
rename to media/apex/java/android/media/session/SessionLink.aidl
diff --git a/media/java/android/media/session/SessionLink.java b/media/apex/java/android/media/session/SessionLink.java
similarity index 98%
rename from media/java/android/media/session/SessionLink.java
rename to media/apex/java/android/media/session/SessionLink.java
index 0da0a5a..4ea7623 100644
--- a/media/java/android/media/session/SessionLink.java
+++ b/media/apex/java/android/media/session/SessionLink.java
@@ -23,8 +23,6 @@
import android.media.AudioAttributes;
import android.media.MediaMetadata;
import android.media.MediaParceledListSlice;
-import android.media.Rating;
-import android.media.VolumeProvider;
import android.media.session.MediaSession.QueueItem;
import android.os.Bundle;
import android.os.IBinder;
@@ -234,7 +232,7 @@
*
* @param type the rating type.
*/
- void setRatingType(@Rating.Style int type) {
+ void setRatingType(int type) {
try {
mISession.setRatingType(type);
} catch (RemoteException e) {
@@ -261,7 +259,7 @@
* @param control the volume control type
* @param max the max volume
*/
- void setPlaybackToRemote(@VolumeProvider.ControlType int control, int max) {
+ void setPlaybackToRemote(int control, int max) {
try {
mISession.setPlaybackToRemote(control, max);
} catch (RemoteException e) {
diff --git a/media/java/android/service/media/IMediaBrowserService.aidl b/media/apex/java/android/service/media/IMediaBrowserService.aidl
similarity index 94%
rename from media/java/android/service/media/IMediaBrowserService.aidl
rename to media/apex/java/android/service/media/IMediaBrowserService.aidl
index 84f41f6..1c50ec7 100644
--- a/media/java/android/service/media/IMediaBrowserService.aidl
+++ b/media/apex/java/android/service/media/IMediaBrowserService.aidl
@@ -2,9 +2,7 @@
package android.service.media;
-import android.content.res.Configuration;
import android.service.media.IMediaBrowserServiceCallbacks;
-import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
diff --git a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl b/media/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
similarity index 96%
rename from media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
rename to media/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
index 8dc480d..507a8f7 100644
--- a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
+++ b/media/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
@@ -2,7 +2,6 @@
package android.service.media;
-import android.graphics.Bitmap;
import android.media.MediaParceledListSlice;
import android.media.session.MediaSession;
import android.os.Bundle;
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/apex/java/android/service/media/MediaBrowserService.java
similarity index 98%
rename from media/java/android/service/media/MediaBrowserService.java
rename to media/apex/java/android/service/media/MediaBrowserService.java
index d19d117..d9ef6ae 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/apex/java/android/service/media/MediaBrowserService.java
@@ -98,7 +98,7 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag=true, value = { RESULT_FLAG_OPTION_NOT_HANDLED,
+ @IntDef(flag = true, value = { RESULT_FLAG_OPTION_NOT_HANDLED,
RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED })
private @interface ResultFlags { }
@@ -291,7 +291,7 @@
final ConnectionRecord connection = mConnections.get(b);
if (connection == null) {
Log.w(TAG, "addSubscription for callback that isn't registered id="
- + id);
+ + id);
return;
}
@@ -301,7 +301,8 @@
}
@Override
- public void removeSubscriptionDeprecated(String id, IMediaBrowserServiceCallbacks callbacks) {
+ public void removeSubscriptionDeprecated(
+ String id, IMediaBrowserServiceCallbacks callbacks) {
// do-nothing
}
@@ -487,7 +488,7 @@
@Override
public void run() {
Iterator<ConnectionRecord> iter = mConnections.values().iterator();
- while (iter.hasNext()){
+ while (iter.hasNext()) {
ConnectionRecord connection = iter.next();
try {
connection.callbacks.onConnect(connection.root.getRootId(), token,
@@ -541,8 +542,7 @@
throw new IllegalStateException("This should be called inside of onGetRoot or"
+ " onLoadChildren or onLoadItem methods");
}
- return new RemoteUserInfo(mCurConnection.pkg, mCurConnection.pid, mCurConnection.uid,
- mCurConnection.callbacks.asBinder());
+ return new RemoteUserInfo(mCurConnection.pkg, mCurConnection.pid, mCurConnection.uid);
}
/**
@@ -608,7 +608,7 @@
final PackageManager pm = getPackageManager();
final String[] packages = pm.getPackagesForUid(uid);
final int N = packages.length;
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
if (packages[i].equals(pkg)) {
return true;
}
@@ -649,7 +649,7 @@
List<Pair<IBinder, Bundle>> callbackList = connection.subscriptions.get(id);
if (callbackList != null) {
Iterator<Pair<IBinder, Bundle>> iter = callbackList.iterator();
- while (iter.hasNext()){
+ while (iter.hasNext()) {
if (token == iter.next().first) {
removed = true;
iter.remove();
@@ -820,8 +820,8 @@
*/
public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
- final private String mRootId;
- final private Bundle mExtras;
+ private final String mRootId;
+ private final Bundle mExtras;
/**
* Constructs a browser root.
@@ -830,8 +830,8 @@
*/
public BrowserRoot(@NonNull String rootId, @Nullable Bundle extras) {
if (rootId == null) {
- throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. " +
- "Use null for BrowserRoot instead.");
+ throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. "
+ + "Use null for BrowserRoot instead.");
}
mRootId = rootId;
mExtras = extras;
diff --git a/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java
index 52771e4..1d763ce 100644
--- a/media/java/android/media/AudioRecordingConfiguration.java
+++ b/media/java/android/media/AudioRecordingConfiguration.java
@@ -356,11 +356,11 @@
dest.writeInt(mDeviceSource);
dest.writeInt(mClientEffects.length);
for (int i = 0; i < mClientEffects.length; i++) {
- mClientEffects[i].writeToParcel(dest, 0);
+ mClientEffects[i].writeToParcel(dest);
}
dest.writeInt(mDeviceEffects.length);
for (int i = 0; i < mDeviceEffects.length; i++) {
- mDeviceEffects[i].writeToParcel(dest, 0);
+ mDeviceEffects[i].writeToParcel(dest);
}
}
@@ -375,13 +375,13 @@
mClientPortId = in.readInt();
mClientSilenced = in.readBoolean();
mDeviceSource = in.readInt();
- mClientEffects = AudioEffect.Descriptor.CREATOR.newArray(in.readInt());
+ mClientEffects = new AudioEffect.Descriptor[in.readInt()];
for (int i = 0; i < mClientEffects.length; i++) {
- mClientEffects[i] = AudioEffect.Descriptor.CREATOR.createFromParcel(in);
+ mClientEffects[i] = new AudioEffect.Descriptor(in);
}
- mDeviceEffects = AudioEffect.Descriptor.CREATOR.newArray(in.readInt());
- for (int i = 0; i < mClientEffects.length; i++) {
- mDeviceEffects[i] = AudioEffect.Descriptor.CREATOR.createFromParcel(in);
+ mDeviceEffects = new AudioEffect.Descriptor[in.readInt()];
+ for (int i = 0; i < mDeviceEffects.length; i++) {
+ mDeviceEffects[i] = new AudioEffect.Descriptor(in);
}
}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 2848b89..af016d5 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1100,7 +1100,8 @@
(1 << STREAM_RING) |
(1 << STREAM_NOTIFICATION) |
(1 << STREAM_SYSTEM) |
- (1 << STREAM_VOICE_CALL);
+ (1 << STREAM_VOICE_CALL) |
+ (1 << STREAM_BLUETOOTH_SCO);
/**
* Event posted by AudioTrack and AudioRecord JNI (JNIDeviceCallback) when routing changes.
diff --git a/media/java/android/media/IRemoteVolumeController.aidl b/media/java/android/media/IRemoteVolumeController.aidl
index a591c11..74c05c4 100644
--- a/media/java/android/media/IRemoteVolumeController.aidl
+++ b/media/java/android/media/IRemoteVolumeController.aidl
@@ -9,6 +9,7 @@
*
* 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.
@@ -16,7 +17,7 @@
package android.media;
-import android.media.session.ISessionController;
+import android.media.session.MediaSession;
/**
* AIDL for the MediaSessionService to report interesting events on remote playback
@@ -25,8 +26,8 @@
* @hide
*/
oneway interface IRemoteVolumeController {
- void remoteVolumeChanged(in ISessionController session, int flags);
+ void remoteVolumeChanged(in MediaSession.Token sessionToken, int flags);
// sets the default session to use with the slider, replaces remoteSliderVisibility
// on IVolumeController
- void updateRemoteController(in ISessionController session);
+ void updateRemoteController(in MediaSession.Token sessionToken);
}
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index bfc10da5..2d2c4a8 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -176,7 +176,8 @@
* @param uuid The UUID of the crypto scheme.
*/
public static final boolean isCryptoSchemeSupported(@NonNull UUID uuid) {
- return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), null);
+ return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), null,
+ SECURITY_LEVEL_UNKNOWN);
}
/**
@@ -189,7 +190,25 @@
*/
public static final boolean isCryptoSchemeSupported(
@NonNull UUID uuid, @NonNull String mimeType) {
- return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), mimeType);
+ return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid),
+ mimeType, SECURITY_LEVEL_UNKNOWN);
+ }
+
+ /**
+ * Query if the given scheme identified by its UUID is supported on
+ * this device, and whether the DRM plugin is able to handle the
+ * media container format specified by mimeType at the requested
+ * security level.
+ *
+ * @param uuid The UUID of the crypto scheme.
+ * @param mimeType The MIME type of the media container, e.g. "video/mp4"
+ * or "video/webm"
+ * @param securityLevel the security level requested
+ */
+ public static final boolean isCryptoSchemeSupported(
+ @NonNull UUID uuid, @NonNull String mimeType, @SecurityLevel int securityLevel) {
+ return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), mimeType,
+ securityLevel);
}
private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
@@ -206,7 +225,7 @@
}
private static final native boolean isCryptoSchemeSupportedNative(
- @NonNull byte[] uuid, @Nullable String mimeType);
+ @NonNull byte[] uuid, @Nullable String mimeType, @SecurityLevel int securityLevel);
private EventHandler createHandler() {
Looper looper;
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 3b51c82..325420b 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -21,18 +21,14 @@
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Bitmap;
+import android.media.session.MediaSession;
import android.media.session.MediaSessionLegacyHelper;
import android.media.session.PlaybackState;
-import android.media.session.MediaSession;
import android.os.Bundle;
-import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
-import java.lang.IllegalArgumentException;
-
/**
* RemoteControlClient enables exposing information meant to be consumed by remote controls
* capable of displaying metadata, artwork and media transport control buttons.
@@ -682,7 +678,7 @@
// USE_SESSIONS
if (mSession != null) {
- int pbState = PlaybackState.getStateFromRccState(state);
+ int pbState = getStateFromRccState(state);
long position = hasPosition ? mPlaybackPositionMs
: PlaybackState.PLAYBACK_POSITION_UNKNOWN;
@@ -718,8 +714,7 @@
// USE_SESSIONS
if (mSession != null) {
PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState);
- bob.setActions(
- PlaybackState.getActionsFromRccControlFlags(transportControlFlags));
+ bob.setActions(getActionsFromRccControlFlags(transportControlFlags));
mSessionPlaybackState = bob.build();
mSession.setPlaybackState(mSessionPlaybackState);
}
@@ -1001,16 +996,19 @@
* Period for playback position drift checks, 15s when playing at 1x or slower.
*/
private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000;
+
/**
* Minimum period for playback position drift checks, never more often when every 2s, when
* fast forwarding or rewinding.
*/
private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000;
+
/**
* The value above which the difference between client-reported playback position and
* estimated position is considered a drift.
*/
private final static long POSITION_DRIFT_MAX_MS = 500;
+
/**
* Compute the period at which the estimated playback position should be compared against the
* actual playback position. Is a funciton of playback speed.
@@ -1025,4 +1023,151 @@
POSITION_REFRESH_PERIOD_MIN_MS);
}
}
+
+ /**
+ * Get the {@link PlaybackState} state for the given
+ * {@link RemoteControlClient} state.
+ *
+ * @param rccState The state used by {@link RemoteControlClient}.
+ * @return The equivalent state used by {@link PlaybackState}.
+ */
+ private static int getStateFromRccState(int rccState) {
+ switch (rccState) {
+ case PLAYSTATE_BUFFERING:
+ return PlaybackState.STATE_BUFFERING;
+ case PLAYSTATE_ERROR:
+ return PlaybackState.STATE_ERROR;
+ case PLAYSTATE_FAST_FORWARDING:
+ return PlaybackState.STATE_FAST_FORWARDING;
+ case PLAYSTATE_NONE:
+ return PlaybackState.STATE_NONE;
+ case PLAYSTATE_PAUSED:
+ return PlaybackState.STATE_PAUSED;
+ case PLAYSTATE_PLAYING:
+ return PlaybackState.STATE_PLAYING;
+ case PLAYSTATE_REWINDING:
+ return PlaybackState.STATE_REWINDING;
+ case PLAYSTATE_SKIPPING_BACKWARDS:
+ return PlaybackState.STATE_SKIPPING_TO_PREVIOUS;
+ case PLAYSTATE_SKIPPING_FORWARDS:
+ return PlaybackState.STATE_SKIPPING_TO_NEXT;
+ case PLAYSTATE_STOPPED:
+ return PlaybackState.STATE_STOPPED;
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * Get the {@link RemoteControlClient} state for the given
+ * {@link PlaybackState} state.
+ *
+ * @param state The state used by {@link PlaybackState}.
+ * @return The equivalent state used by {@link RemoteControlClient}.
+ */
+ static int getRccStateFromState(int state) {
+ switch (state) {
+ case PlaybackState.STATE_BUFFERING:
+ return PLAYSTATE_BUFFERING;
+ case PlaybackState.STATE_ERROR:
+ return PLAYSTATE_ERROR;
+ case PlaybackState.STATE_FAST_FORWARDING:
+ return PLAYSTATE_FAST_FORWARDING;
+ case PlaybackState.STATE_NONE:
+ return PLAYSTATE_NONE;
+ case PlaybackState.STATE_PAUSED:
+ return PLAYSTATE_PAUSED;
+ case PlaybackState.STATE_PLAYING:
+ return PLAYSTATE_PLAYING;
+ case PlaybackState.STATE_REWINDING:
+ return PLAYSTATE_REWINDING;
+ case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+ return PLAYSTATE_SKIPPING_BACKWARDS;
+ case PlaybackState.STATE_SKIPPING_TO_NEXT:
+ return PLAYSTATE_SKIPPING_FORWARDS;
+ case PlaybackState.STATE_STOPPED:
+ return PLAYSTATE_STOPPED;
+ default:
+ return -1;
+ }
+ }
+
+ private static long getActionsFromRccControlFlags(int rccFlags) {
+ long actions = 0;
+ long flag = 1;
+ while (flag <= rccFlags) {
+ if ((flag & rccFlags) != 0) {
+ actions |= getActionForRccFlag((int) flag);
+ }
+ flag = flag << 1;
+ }
+ return actions;
+ }
+
+ static int getRccControlFlagsFromActions(long actions) {
+ int rccFlags = 0;
+ long action = 1;
+ while (action <= actions && action < Integer.MAX_VALUE) {
+ if ((action & actions) != 0) {
+ rccFlags |= getRccFlagForAction(action);
+ }
+ action = action << 1;
+ }
+ return rccFlags;
+ }
+
+ private static long getActionForRccFlag(int flag) {
+ switch (flag) {
+ case FLAG_KEY_MEDIA_PREVIOUS:
+ return PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+ case FLAG_KEY_MEDIA_REWIND:
+ return PlaybackState.ACTION_REWIND;
+ case FLAG_KEY_MEDIA_PLAY:
+ return PlaybackState.ACTION_PLAY;
+ case FLAG_KEY_MEDIA_PLAY_PAUSE:
+ return PlaybackState.ACTION_PLAY_PAUSE;
+ case FLAG_KEY_MEDIA_PAUSE:
+ return PlaybackState.ACTION_PAUSE;
+ case FLAG_KEY_MEDIA_STOP:
+ return PlaybackState.ACTION_STOP;
+ case FLAG_KEY_MEDIA_FAST_FORWARD:
+ return PlaybackState.ACTION_FAST_FORWARD;
+ case FLAG_KEY_MEDIA_NEXT:
+ return PlaybackState.ACTION_SKIP_TO_NEXT;
+ case FLAG_KEY_MEDIA_POSITION_UPDATE:
+ return PlaybackState.ACTION_SEEK_TO;
+ case FLAG_KEY_MEDIA_RATING:
+ return PlaybackState.ACTION_SET_RATING;
+ }
+ return 0;
+ }
+
+ private static int getRccFlagForAction(long action) {
+ // We only care about the lower set of actions that can map to rcc
+ // flags.
+ int testAction = action < Integer.MAX_VALUE ? (int) action : 0;
+ switch (testAction) {
+ case (int) PlaybackState.ACTION_SKIP_TO_PREVIOUS:
+ return FLAG_KEY_MEDIA_PREVIOUS;
+ case (int) PlaybackState.ACTION_REWIND:
+ return FLAG_KEY_MEDIA_REWIND;
+ case (int) PlaybackState.ACTION_PLAY:
+ return FLAG_KEY_MEDIA_PLAY;
+ case (int) PlaybackState.ACTION_PLAY_PAUSE:
+ return FLAG_KEY_MEDIA_PLAY_PAUSE;
+ case (int) PlaybackState.ACTION_PAUSE:
+ return FLAG_KEY_MEDIA_PAUSE;
+ case (int) PlaybackState.ACTION_STOP:
+ return FLAG_KEY_MEDIA_STOP;
+ case (int) PlaybackState.ACTION_FAST_FORWARD:
+ return FLAG_KEY_MEDIA_FAST_FORWARD;
+ case (int) PlaybackState.ACTION_SKIP_TO_NEXT:
+ return FLAG_KEY_MEDIA_NEXT;
+ case (int) PlaybackState.ACTION_SEEK_TO:
+ return FLAG_KEY_MEDIA_POSITION_UPDATE;
+ case (int) PlaybackState.ACTION_SET_RATING:
+ return FLAG_KEY_MEDIA_RATING;
+ }
+ return 0;
+ }
}
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index 5e9eed7..f70963a 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -632,8 +632,8 @@
l = this.mOnClientUpdateListener;
}
if (l != null) {
- int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE : PlaybackState
- .getRccStateFromState(state.getState());
+ int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE
+ : RemoteControlClient.getRccStateFromState(state.getState());
if (state == null || state.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
l.onClientPlaybackStateUpdate(playstate);
} else {
@@ -642,7 +642,7 @@
}
if (state != null) {
l.onClientTransportControlUpdate(
- PlaybackState.getRccControlFlagsFromActions(state.getActions()));
+ RemoteControlClient.getRccControlFlagsFromActions(state.getActions()));
}
}
}
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 42597aa..73d3d88 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -138,7 +138,7 @@
mAudioAttributes = attributes;
// The audio attributes have to be set before the media player is prepared.
// Re-initialize it.
- setUri(mUri);
+ setUri(mUri, mVolumeShaperConfig);
}
/**
@@ -415,6 +415,7 @@
mLocalPlayer.reset();
mLocalPlayer.release();
mLocalPlayer = null;
+ mVolumeShaper = null;
synchronized (sActiveRingtones) {
sActiveRingtones.remove(this);
}
diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index 52e9ae1..5b4bbce 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -26,7 +26,6 @@
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
-import android.os.Parcelable;
import android.util.Log;
import java.lang.ref.WeakReference;
@@ -229,7 +228,7 @@
* The method {@link #queryEffects()} returns an array of Descriptors to facilitate effects
* enumeration.
*/
- public static final class Descriptor implements Parcelable {
+ public static class Descriptor {
public Descriptor() {
}
@@ -294,7 +293,9 @@
this.implementor = implementor;
}
- private Descriptor(Parcel in) {
+ /** @hide */
+ @TestApi
+ public Descriptor(Parcel in) {
type = UUID.fromString(in.readString());
uuid = UUID.fromString(in.readString());
connectMode = in.readString();
@@ -302,33 +303,14 @@
implementor = in.readString();
}
- public static final Parcelable.Creator<Descriptor> CREATOR =
- new Parcelable.Creator<Descriptor>() {
- /**
- * Rebuilds a Descriptor previously stored with writeToParcel().
- * @param p Parcel object to read the Descriptor from
- * @return a new Descriptor created from the data in the parcel
- */
- public Descriptor createFromParcel(Parcel p) {
- return new Descriptor(p);
- }
- public Descriptor[] newArray(int size) {
- return new Descriptor[size];
- }
- };
-
@Override
public int hashCode() {
return Objects.hash(type, uuid, connectMode, name, implementor);
}
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
+ /** @hide */
+ @TestApi
+ public void writeToParcel(Parcel dest) {
dest.writeString(type.toString());
dest.writeString(uuid.toString());
dest.writeString(connectMode);
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index f02d9ba..1a185e9 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.PendingIntent;
@@ -33,23 +34,15 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ResultReceiver;
import android.service.media.MediaBrowserService;
import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-import android.view.KeyEvent;
-import android.view.ViewConfiguration;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
import java.util.List;
-import java.util.Objects;
/**
* Allows interaction with media controllers, volume keys, media buttons, and
@@ -74,7 +67,7 @@
* MediaSession objects are thread safe.
*/
public final class MediaSession {
- private static final String TAG = "MediaSession";
+ static final String TAG = "MediaSession";
/**
* Set this flag on the session to indicate that it can handle media button
@@ -121,21 +114,11 @@
FLAG_EXCLUSIVE_GLOBAL_PRIORITY })
public @interface SessionFlags { }
- private final Object mLock = new Object();
- private final int mMaxBitmapSize;
-
- private final MediaSession.Token mSessionToken;
- private final MediaController mController;
- private final SessionLink mSessionLink;
- private final SessionCallbackLink mCbStub;
+ private final MediaSessionEngine mImpl;
// Do not change the name of mCallback. Support lib accesses this by using reflection.
@UnsupportedAppUsage
- private CallbackMessageHandler mCallback;
- private VolumeProvider mVolumeProvider;
- private PlaybackState mPlaybackState;
-
- private boolean mActive = false;
+ private Object mCallback;
/**
* Creates a new session. The session will automatically be registered with
@@ -153,15 +136,15 @@
if (TextUtils.isEmpty(tag)) {
throw new IllegalArgumentException("tag cannot be null or empty");
}
- mMaxBitmapSize = context.getResources().getDimensionPixelSize(
- android.R.dimen.config_mediaMetadataBitmapMaxSize);
- mCbStub = new SessionCallbackLink(context, new CallbackStub(this));
MediaSessionManager manager = (MediaSessionManager) context
.getSystemService(Context.MEDIA_SESSION_SERVICE);
try {
- mSessionLink = manager.createSession(mCbStub, tag);
- mSessionToken = new Token(mSessionLink.getController());
- mController = new MediaController(context, mSessionToken);
+ MediaSessionEngine.CallbackStub cbStub = new MediaSessionEngine.CallbackStub();
+ SessionCallbackLink cbLink = new SessionCallbackLink(context, cbStub);
+ SessionLink sessionLink = manager.createSession(cbLink, tag);
+ mImpl = new MediaSessionEngine(context, sessionLink, cbLink, cbStub,
+ context.getResources().getDimensionPixelSize(
+ android.R.dimen.config_mediaMetadataBitmapMaxSize));
} catch (RuntimeException e) {
throw new RuntimeException("Remote error creating session.", e);
}
@@ -177,7 +160,8 @@
* @param callback The callback object
*/
public void setCallback(@Nullable Callback callback) {
- setCallback(callback, null);
+ mCallback = callback == null ? null : new Object();
+ mImpl.setCallback(callback);
}
/**
@@ -190,24 +174,8 @@
* @param handler The handler that events should be posted on.
*/
public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
- synchronized (mLock) {
- if (mCallback != null) {
- // We're updating the callback, clear the session from the old one.
- mCallback.mCallback.mSession = null;
- mCallback.removeCallbacksAndMessages(null);
- }
- if (callback == null) {
- mCallback = null;
- return;
- }
- if (handler == null) {
- handler = new Handler();
- }
- callback.mSession = this;
- CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
- callback);
- mCallback = msgHandler;
- }
+ mCallback = callback == null ? null : new Object();
+ mImpl.setCallback(callback, handler);
}
/**
@@ -218,11 +186,7 @@
* @param pi The intent to launch to show UI for this Session.
*/
public void setSessionActivity(@Nullable PendingIntent pi) {
- try {
- mSessionLink.setLaunchPendingIntent(pi);
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e);
- }
+ mImpl.setSessionActivity(pi);
}
/**
@@ -234,11 +198,7 @@
* @param mbr The {@link PendingIntent} to send the media button event to.
*/
public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
- try {
- mSessionLink.setMediaButtonReceiver(mbr);
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
- }
+ mImpl.setMediaButtonReceiver(mbr);
}
/**
@@ -247,11 +207,7 @@
* @param flags The flags to set for this session.
*/
public void setFlags(@SessionFlags int flags) {
- try {
- mSessionLink.setFlags(flags);
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Failure in setFlags.", e);
- }
+ mImpl.setFlags(flags);
}
/**
@@ -266,14 +222,7 @@
* @param attributes The {@link AudioAttributes} for this session's audio.
*/
public void setPlaybackToLocal(AudioAttributes attributes) {
- if (attributes == null) {
- throw new IllegalArgumentException("Attributes cannot be null for local playback.");
- }
- try {
- mSessionLink.setPlaybackToLocal(attributes);
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
- }
+ mImpl.setPlaybackToLocal(attributes);
}
/**
@@ -288,26 +237,7 @@
* not be null.
*/
public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) {
- if (volumeProvider == null) {
- throw new IllegalArgumentException("volumeProvider may not be null!");
- }
- synchronized (mLock) {
- mVolumeProvider = volumeProvider;
- }
- volumeProvider.setCallback(new VolumeProvider.Callback() {
- @Override
- public void onVolumeChanged(VolumeProvider volumeProvider) {
- notifyRemoteVolumeChanged(volumeProvider);
- }
- });
-
- try {
- mSessionLink.setPlaybackToRemote(volumeProvider.getVolumeControl(),
- volumeProvider.getMaxVolume());
- mSessionLink.setCurrentVolume(volumeProvider.getCurrentVolume());
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
- }
+ mImpl.setPlaybackToRemote(volumeProvider);
}
/**
@@ -319,15 +249,7 @@
* @param active Whether this session is active or not.
*/
public void setActive(boolean active) {
- if (mActive == active) {
- return;
- }
- try {
- mSessionLink.setActive(active);
- mActive = active;
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Failure in setActive.", e);
- }
+ mImpl.setActive(active);
}
/**
@@ -336,7 +258,7 @@
* @return True if the session is active, false otherwise.
*/
public boolean isActive() {
- return mActive;
+ return mImpl.isActive();
}
/**
@@ -348,14 +270,7 @@
* @param extras Any extras included with the event
*/
public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) {
- if (TextUtils.isEmpty(event)) {
- throw new IllegalArgumentException("event cannot be null or empty");
- }
- try {
- mSessionLink.sendEvent(event, extras);
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Error sending event", e);
- }
+ mImpl.sendSessionEvent(event, extras);
}
/**
@@ -364,11 +279,7 @@
* but it must be released if your activity or service is being destroyed.
*/
public void release() {
- try {
- mSessionLink.destroySession();
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Error releasing session: ", e);
- }
+ mImpl.close();
}
/**
@@ -380,7 +291,7 @@
* session
*/
public @NonNull Token getSessionToken() {
- return mSessionToken;
+ return mImpl.getSessionToken();
}
/**
@@ -390,7 +301,7 @@
* @return A controller for this session.
*/
public @NonNull MediaController getController() {
- return mController;
+ return mImpl.getController();
}
/**
@@ -399,12 +310,7 @@
* @param state The current state of playback
*/
public void setPlaybackState(@Nullable PlaybackState state) {
- mPlaybackState = state;
- try {
- mSessionLink.setPlaybackState(state);
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Dead object in setPlaybackState.", e);
- }
+ mImpl.setPlaybackState(state);
}
/**
@@ -416,24 +322,7 @@
* @see android.media.MediaMetadata.Builder#putBitmap
*/
public void setMetadata(@Nullable MediaMetadata metadata) {
- long duration = -1;
- int fields = 0;
- MediaDescription description = null;
- if (metadata != null) {
- metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
- if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
- duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
- }
- fields = metadata.size();
- description = metadata.getDescription();
- }
- String metadataDescription = "size=" + fields + ", description=" + description;
-
- try {
- mSessionLink.setMetadata(metadata, duration, metadataDescription);
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Dead object in setPlaybackState.", e);
- }
+ mImpl.setMetadata(metadata);
}
/**
@@ -448,11 +337,7 @@
* @param queue A list of items in the play queue.
*/
public void setQueue(@Nullable List<QueueItem> queue) {
- try {
- mSessionLink.setQueue(queue);
- } catch (RuntimeException e) {
- Log.wtf("Dead object in setQueue.", e);
- }
+ mImpl.setQueue(queue);
}
/**
@@ -463,11 +348,7 @@
* @param title The title of the play queue.
*/
public void setQueueTitle(@Nullable CharSequence title) {
- try {
- mSessionLink.setQueueTitle(title);
- } catch (RuntimeException e) {
- Log.wtf("Dead object in setQueueTitle.", e);
- }
+ mImpl.setQueueTitle(title);
}
/**
@@ -484,11 +365,7 @@
* </ul>
*/
public void setRatingType(@Rating.Style int type) {
- try {
- mSessionLink.setRatingType(type);
- } catch (RuntimeException e) {
- Log.e(TAG, "Error in setRatingType.", e);
- }
+ mImpl.setRatingType(type);
}
/**
@@ -499,11 +376,7 @@
* @param extras The extras associated with the {@link MediaSession}.
*/
public void setExtras(@Nullable Bundle extras) {
- try {
- mSessionLink.setExtras(extras);
- } catch (RuntimeException e) {
- Log.wtf("Dead object in setExtras.", e);
- }
+ mImpl.setExtras(extras);
}
/**
@@ -515,11 +388,7 @@
* @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo)
*/
public final @NonNull RemoteUserInfo getCurrentControllerInfo() {
- if (mCallback == null || mCallback.mCurrentControllerInfo == null) {
- throw new IllegalStateException(
- "This should be called inside of MediaSession.Callback methods");
- }
- return mCallback.mCurrentControllerInfo;
+ return mImpl.getCurrentControllerInfo();
}
/**
@@ -529,17 +398,7 @@
* @hide
*/
public void notifyRemoteVolumeChanged(VolumeProvider provider) {
- synchronized (mLock) {
- if (provider == null || provider != mVolumeProvider) {
- Log.w(TAG, "Received update from stale volume provider");
- return;
- }
- }
- try {
- mSessionLink.setCurrentVolume(provider.getCurrentVolume());
- } catch (RuntimeException e) {
- Log.e(TAG, "Error in notifyVolumeChanged", e);
- }
+ mImpl.notifyRemoteVolumeChanged(provider);
}
/**
@@ -551,119 +410,7 @@
*/
@UnsupportedAppUsage
public String getCallingPackage() {
- if (mCallback != null && mCallback.mCurrentControllerInfo != null) {
- return mCallback.mCurrentControllerInfo.getPackageName();
- }
- return null;
- }
-
- private void dispatchPrepare(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_PREPARE, null, null);
- }
-
- private void dispatchPrepareFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
- }
-
- private void dispatchPrepareFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras);
- }
-
- private void dispatchPrepareFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_URI, uri, extras);
- }
-
- private void dispatchPlay(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_PLAY, null, null);
- }
-
- private void dispatchPlayFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
- }
-
- private void dispatchPlayFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
- }
-
- private void dispatchPlayFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
- }
-
- private void dispatchSkipToItem(RemoteUserInfo caller, long id) {
- postToCallback(caller, CallbackMessageHandler.MSG_SKIP_TO_ITEM, id, null);
- }
-
- private void dispatchPause(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_PAUSE, null, null);
- }
-
- private void dispatchStop(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_STOP, null, null);
- }
-
- private void dispatchNext(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_NEXT, null, null);
- }
-
- private void dispatchPrevious(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_PREVIOUS, null, null);
- }
-
- private void dispatchFastForward(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_FAST_FORWARD, null, null);
- }
-
- private void dispatchRewind(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_REWIND, null, null);
- }
-
- private void dispatchSeekTo(RemoteUserInfo caller, long pos) {
- postToCallback(caller, CallbackMessageHandler.MSG_SEEK_TO, pos, null);
- }
-
- private void dispatchRate(RemoteUserInfo caller, Rating rating) {
- postToCallback(caller, CallbackMessageHandler.MSG_RATE, rating, null);
- }
-
- private void dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args) {
- postToCallback(caller, CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
- }
-
- private void dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent) {
- postToCallback(caller, CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, null);
- }
-
- private void dispatchMediaButtonDelayed(RemoteUserInfo info, Intent mediaButtonIntent,
- long delay) {
- postToCallbackDelayed(info, CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT,
- mediaButtonIntent, null, delay);
- }
-
- private void dispatchAdjustVolume(RemoteUserInfo caller, int direction) {
- postToCallback(caller, CallbackMessageHandler.MSG_ADJUST_VOLUME, direction, null);
- }
-
- private void dispatchSetVolumeTo(RemoteUserInfo caller, int volume) {
- postToCallback(caller, CallbackMessageHandler.MSG_SET_VOLUME, volume, null);
- }
-
- private void dispatchCommand(RemoteUserInfo caller, String command, Bundle args,
- ResultReceiver resultCb) {
- Command cmd = new Command(command, args, resultCb);
- postToCallback(caller, CallbackMessageHandler.MSG_COMMAND, cmd, null);
- }
-
- private void postToCallback(RemoteUserInfo caller, int what, Object obj, Bundle data) {
- postToCallbackDelayed(caller, what, obj, data, 0);
- }
-
- private void postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data,
- long delay) {
- synchronized (mLock) {
- if (mCallback != null) {
- mCallback.post(caller, what, obj, data, delay);
- }
- }
+ return mImpl.getCallingPackage();
}
/**
@@ -672,17 +419,7 @@
* @hide
*/
public static boolean isActiveState(int state) {
- switch (state) {
- case PlaybackState.STATE_FAST_FORWARDING:
- case PlaybackState.STATE_REWINDING:
- case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
- case PlaybackState.STATE_SKIPPING_TO_NEXT:
- case PlaybackState.STATE_BUFFERING:
- case PlaybackState.STATE_CONNECTING:
- case PlaybackState.STATE_PLAYING:
- return true;
- }
- return false;
+ return MediaSessionEngine.isActiveState(state);
}
/**
@@ -692,13 +429,13 @@
*/
public static final class Token implements Parcelable {
- private ControllerLink mBinder;
+ private ControllerLink mControllerLink;
/**
* @hide
*/
- public Token(ControllerLink binder) {
- mBinder = binder;
+ public Token(ControllerLink controllerLink) {
+ mControllerLink = controllerLink;
}
@Override
@@ -708,14 +445,15 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(mBinder, flags);
+ dest.writeParcelable(mControllerLink, flags);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
- result = prime * result + ((mBinder == null) ? 0 : mBinder.getBinder().hashCode());
+ result = prime * result + ((mControllerLink == null)
+ ? 0 : mControllerLink.getBinder().hashCode());
return result;
}
@@ -728,17 +466,23 @@
if (getClass() != obj.getClass())
return false;
Token other = (Token) obj;
- if (mBinder == null) {
- if (other.mBinder != null)
+ if (mControllerLink == null) {
+ if (other.mControllerLink != null) {
return false;
- } else if (!mBinder.getBinder().equals(other.mBinder.getBinder())) {
+ }
+ } else if (!mControllerLink.getBinder().equals(other.mControllerLink.getBinder())) {
return false;
}
return true;
}
- ControllerLink getBinder() {
- return mBinder;
+ /**
+ * Gets the controller link in this token.
+ * @hide
+ */
+ @SystemApi
+ public ControllerLink getControllerLink() {
+ return mControllerLink;
}
public static final Parcelable.Creator<Token> CREATOR =
@@ -762,9 +506,7 @@
*/
public abstract static class Callback {
- private MediaSession mSession;
- private CallbackMessageHandler mHandler;
- private boolean mMediaPlayPauseKeyPending;
+ MediaSessionEngine.MediaButtonEventDelegate mMediaButtonEventDelegate;
public Callback() {
}
@@ -796,110 +538,12 @@
* @return True if the event was handled, false otherwise.
*/
public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
- if (mSession != null && mHandler != null
- && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
- KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
- if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
- PlaybackState state = mSession.mPlaybackState;
- long validActions = state == null ? 0 : state.getActions();
- switch (ke.getKeyCode()) {
- case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
- case KeyEvent.KEYCODE_HEADSETHOOK:
- if (ke.getRepeatCount() > 0) {
- // Consider long-press as a single tap.
- handleMediaPlayPauseKeySingleTapIfPending();
- } else if (mMediaPlayPauseKeyPending) {
- // Consider double tap as the next.
- mHandler.removeMessages(CallbackMessageHandler
- .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
- mMediaPlayPauseKeyPending = false;
- if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
- onSkipToNext();
- }
- } else {
- mMediaPlayPauseKeyPending = true;
- mSession.dispatchMediaButtonDelayed(
- mSession.getCurrentControllerInfo(),
- mediaButtonIntent, ViewConfiguration.getDoubleTapTimeout());
- }
- return true;
- default:
- // If another key is pressed within double tap timeout, consider the
- // pending play/pause as a single tap to handle media keys in order.
- handleMediaPlayPauseKeySingleTapIfPending();
- break;
- }
-
- switch (ke.getKeyCode()) {
- case KeyEvent.KEYCODE_MEDIA_PLAY:
- if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
- onPlay();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_PAUSE:
- if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
- onPause();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_NEXT:
- if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
- onSkipToNext();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
- if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
- onSkipToPrevious();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_STOP:
- if ((validActions & PlaybackState.ACTION_STOP) != 0) {
- onStop();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
- if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
- onFastForward();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_REWIND:
- if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
- onRewind();
- return true;
- }
- break;
- }
- }
+ if (mMediaButtonEventDelegate != null) {
+ return mMediaButtonEventDelegate.onMediaButtonIntent(mediaButtonIntent);
}
return false;
}
- private void handleMediaPlayPauseKeySingleTapIfPending() {
- if (!mMediaPlayPauseKeyPending) {
- return;
- }
- mMediaPlayPauseKeyPending = false;
- mHandler.removeMessages(CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
- PlaybackState state = mSession.mPlaybackState;
- long validActions = state == null ? 0 : state.getActions();
- boolean isPlaying = state != null
- && state.getState() == PlaybackState.STATE_PLAYING;
- boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
- | PlaybackState.ACTION_PLAY)) != 0;
- boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
- | PlaybackState.ACTION_PAUSE)) != 0;
- if (isPlaying && canPause) {
- onPause();
- } else if (!isPlaying && canPlay) {
- onPlay();
- }
- }
-
/**
* Override to handle requests to prepare playback. During the preparation, a session should
* not hold audio focus in order to allow other sessions play seamlessly. The state of
@@ -1042,251 +686,14 @@
*/
public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
}
- }
- /**
- * @hide
- */
- public static final class CallbackStub extends SessionCallbackLink.CallbackStub {
- private WeakReference<MediaSession> mMediaSession;
-
- public CallbackStub(MediaSession session) {
- mMediaSession = new WeakReference<>(session);
- }
-
- private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- return new RemoteUserInfo(packageName, pid, uid,
- caller != null ? caller.getBinder() : null);
- }
-
- @Override
- public void onCommand(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchCommand(createRemoteUserInfo(packageName, pid, uid, caller),
- command, args, cb);
- }
- }
-
- @Override
- public void onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent,
- int sequenceNumber, ResultReceiver cb) {
- MediaSession session = mMediaSession.get();
- try {
- if (session != null) {
- session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, null),
- mediaButtonIntent);
- }
- } finally {
- if (cb != null) {
- cb.send(sequenceNumber, null);
- }
- }
- }
-
- @Override
- public void onMediaButtonFromController(String packageName, int pid, int uid,
- ControllerCallbackLink caller, Intent mediaButtonIntent) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, caller),
- mediaButtonIntent);
- }
- }
-
- @Override
- public void onPrepare(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onPrepareFromMediaId(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String mediaId,
- Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPrepareFromMediaId(
- createRemoteUserInfo(packageName, pid, uid, caller), mediaId, extras);
- }
- }
-
- @Override
- public void onPrepareFromSearch(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String query,
- Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPrepareFromSearch(
- createRemoteUserInfo(packageName, pid, uid, caller), query, extras);
- }
- }
-
- @Override
- public void onPrepareFromUri(String packageName, int pid, int uid,
- ControllerCallbackLink caller, Uri uri, Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPrepareFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
- uri, extras);
- }
- }
-
- @Override
- public void onPlay(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onPlayFromMediaId(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String mediaId,
- Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPlayFromMediaId(createRemoteUserInfo(packageName, pid, uid, caller),
- mediaId, extras);
- }
- }
-
- @Override
- public void onPlayFromSearch(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String query,
- Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPlayFromSearch(createRemoteUserInfo(packageName, pid, uid, caller),
- query, extras);
- }
- }
-
- @Override
- public void onPlayFromUri(String packageName, int pid, int uid,
- ControllerCallbackLink caller, Uri uri, Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPlayFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
- uri, extras);
- }
- }
-
- @Override
- public void onSkipToTrack(String packageName, int pid, int uid,
- ControllerCallbackLink caller, long id) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchSkipToItem(createRemoteUserInfo(packageName, pid, uid, caller), id);
- }
- }
-
- @Override
- public void onPause(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPause(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onStop(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchStop(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onNext(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchNext(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onPrevious(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onFastForward(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchFastForward(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onRewind(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchRewind(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onSeekTo(String packageName, int pid, int uid,
- ControllerCallbackLink caller, long pos) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchSeekTo(createRemoteUserInfo(packageName, pid, uid, caller), pos);
- }
- }
-
- @Override
- public void onRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
- Rating rating) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchRate(createRemoteUserInfo(packageName, pid, uid, caller), rating);
- }
- }
-
- @Override
- public void onCustomAction(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String action, Bundle args) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchCustomAction(createRemoteUserInfo(packageName, pid, uid, caller),
- action, args);
- }
- }
-
- @Override
- public void onAdjustVolume(String packageName, int pid, int uid,
- ControllerCallbackLink caller, int direction) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchAdjustVolume(createRemoteUserInfo(packageName, pid, uid, caller),
- direction);
- }
- }
-
- @Override
- public void onSetVolumeTo(String packageName, int pid, int uid,
- ControllerCallbackLink caller, int value) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchSetVolumeTo(createRemoteUserInfo(packageName, pid, uid, caller),
- value);
- }
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void onSetMediaButtonEventDelegate(
+ @NonNull MediaSessionEngine.MediaButtonEventDelegate delegate) {
+ mMediaButtonEventDelegate = delegate;
}
}
@@ -1300,7 +707,7 @@
*/
public static final int UNKNOWN_ID = -1;
- private final MediaDescription mDescription;
+ private final MediaSessionEngine.QueueItem mImpl;
@UnsupportedAppUsage
private final long mId;
@@ -1312,39 +719,32 @@
* play queue and cannot be {@link #UNKNOWN_ID}.
*/
public QueueItem(MediaDescription description, long id) {
- if (description == null) {
- throw new IllegalArgumentException("Description cannot be null.");
- }
- if (id == UNKNOWN_ID) {
- throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
- }
- mDescription = description;
+ mImpl = new MediaSessionEngine.QueueItem(description, id);
mId = id;
}
private QueueItem(Parcel in) {
- mDescription = MediaDescription.CREATOR.createFromParcel(in);
- mId = in.readLong();
+ mImpl = new MediaSessionEngine.QueueItem(in);
+ mId = mImpl.getQueueId();
}
/**
* Get the description for this item.
*/
public MediaDescription getDescription() {
- return mDescription;
+ return mImpl.getDescription();
}
/**
* Get the queue id for this item.
*/
public long getQueueId() {
- return mId;
+ return mImpl.getQueueId();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
- mDescription.writeToParcel(dest, flags);
- dest.writeLong(mId);
+ mImpl.writeToParcel(dest, flags);
}
@Override
@@ -1368,9 +768,7 @@
@Override
public String toString() {
- return "MediaSession.QueueItem {" +
- "Description=" + mDescription +
- ", Id=" + mId + " }";
+ return mImpl.toString();
}
@Override
@@ -1383,167 +781,7 @@
return false;
}
- final QueueItem item = (QueueItem) o;
- if (mId != item.mId) {
- return false;
- }
-
- if (!Objects.equals(mDescription, item.mDescription)) {
- return false;
- }
-
- return true;
- }
- }
-
- private static final class Command {
- public final String command;
- public final Bundle extras;
- public final ResultReceiver stub;
-
- public Command(String command, Bundle extras, ResultReceiver stub) {
- this.command = command;
- this.extras = extras;
- this.stub = stub;
- }
- }
-
- private class CallbackMessageHandler extends Handler {
- private static final int MSG_COMMAND = 1;
- private static final int MSG_MEDIA_BUTTON = 2;
- private static final int MSG_PREPARE = 3;
- private static final int MSG_PREPARE_MEDIA_ID = 4;
- private static final int MSG_PREPARE_SEARCH = 5;
- private static final int MSG_PREPARE_URI = 6;
- private static final int MSG_PLAY = 7;
- private static final int MSG_PLAY_MEDIA_ID = 8;
- private static final int MSG_PLAY_SEARCH = 9;
- private static final int MSG_PLAY_URI = 10;
- private static final int MSG_SKIP_TO_ITEM = 11;
- private static final int MSG_PAUSE = 12;
- private static final int MSG_STOP = 13;
- private static final int MSG_NEXT = 14;
- private static final int MSG_PREVIOUS = 15;
- private static final int MSG_FAST_FORWARD = 16;
- private static final int MSG_REWIND = 17;
- private static final int MSG_SEEK_TO = 18;
- private static final int MSG_RATE = 19;
- private static final int MSG_CUSTOM_ACTION = 20;
- private static final int MSG_ADJUST_VOLUME = 21;
- private static final int MSG_SET_VOLUME = 22;
- private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 23;
-
- private MediaSession.Callback mCallback;
- private RemoteUserInfo mCurrentControllerInfo;
-
- public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
- super(looper);
- mCallback = callback;
- mCallback.mHandler = this;
- }
-
- public void post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs) {
- Pair<RemoteUserInfo, Object> objWithCaller = Pair.create(caller, obj);
- Message msg = obtainMessage(what, objWithCaller);
- msg.setAsynchronous(true);
- msg.setData(data);
- if (delayMs > 0) {
- sendMessageDelayed(msg, delayMs);
- } else {
- sendMessage(msg);
- }
- }
-
- @Override
- public void handleMessage(Message msg) {
- mCurrentControllerInfo = ((Pair<RemoteUserInfo, Object>) msg.obj).first;
-
- VolumeProvider vp;
- Object obj = ((Pair<RemoteUserInfo, Object>) msg.obj).second;
-
- switch (msg.what) {
- case MSG_COMMAND:
- Command cmd = (Command) obj;
- mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
- break;
- case MSG_MEDIA_BUTTON:
- mCallback.onMediaButtonEvent((Intent) obj);
- break;
- case MSG_PREPARE:
- mCallback.onPrepare();
- break;
- case MSG_PREPARE_MEDIA_ID:
- mCallback.onPrepareFromMediaId((String) obj, msg.getData());
- break;
- case MSG_PREPARE_SEARCH:
- mCallback.onPrepareFromSearch((String) obj, msg.getData());
- break;
- case MSG_PREPARE_URI:
- mCallback.onPrepareFromUri((Uri) obj, msg.getData());
- break;
- case MSG_PLAY:
- mCallback.onPlay();
- break;
- case MSG_PLAY_MEDIA_ID:
- mCallback.onPlayFromMediaId((String) obj, msg.getData());
- break;
- case MSG_PLAY_SEARCH:
- mCallback.onPlayFromSearch((String) obj, msg.getData());
- break;
- case MSG_PLAY_URI:
- mCallback.onPlayFromUri((Uri) obj, msg.getData());
- break;
- case MSG_SKIP_TO_ITEM:
- mCallback.onSkipToQueueItem((Long) obj);
- break;
- case MSG_PAUSE:
- mCallback.onPause();
- break;
- case MSG_STOP:
- mCallback.onStop();
- break;
- case MSG_NEXT:
- mCallback.onSkipToNext();
- break;
- case MSG_PREVIOUS:
- mCallback.onSkipToPrevious();
- break;
- case MSG_FAST_FORWARD:
- mCallback.onFastForward();
- break;
- case MSG_REWIND:
- mCallback.onRewind();
- break;
- case MSG_SEEK_TO:
- mCallback.onSeekTo((Long) obj);
- break;
- case MSG_RATE:
- mCallback.onSetRating((Rating) obj);
- break;
- case MSG_CUSTOM_ACTION:
- mCallback.onCustomAction((String) obj, msg.getData());
- break;
- case MSG_ADJUST_VOLUME:
- synchronized (mLock) {
- vp = mVolumeProvider;
- }
- if (vp != null) {
- vp.onAdjustVolume((int) obj);
- }
- break;
- case MSG_SET_VOLUME:
- synchronized (mLock) {
- vp = mVolumeProvider;
- }
- if (vp != null) {
- vp.onSetVolumeTo((int) obj);
- }
- break;
- case MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT:
- mCallback.handleMediaPlayPauseKeySingleTapIfPending();
- break;
- }
- mCurrentControllerInfo = null;
+ return mImpl.equals(((QueueItem) o).mImpl);
}
}
}
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 77e758fc..c64c452 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -28,7 +28,6 @@
import android.media.IRemoteVolumeController;
import android.media.MediaSession2;
import android.media.Session2Token;
-import android.media.browse.MediaBrowser;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -37,6 +36,7 @@
import android.os.UserHandle;
import android.service.media.MediaBrowserService;
import android.service.notification.NotificationListenerService;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.view.KeyEvent;
@@ -122,7 +122,6 @@
* {@link MediaSession2.Builder} instead.
*
* @param token newly created session2 token
- * @hide
*/
public void notifySession2Created(@NonNull Session2Token token) {
if (token == null) {
@@ -196,9 +195,7 @@
* reject your uses of {@link MediaSession2}.
*
* @return A list of {@link Session2Token}.
- * @hide
*/
- // TODO: unhide
@NonNull
public List<Session2Token> getSession2Tokens() {
return getSession2Tokens(UserHandle.myUserId());
@@ -209,13 +206,12 @@
* given user.
* <p>
* If you want to get tokens for another user, you must hold the
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
+ * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL permission.
*
* @param userId The user id to fetch sessions for.
* @return A list of {@link Session2Token}
* @hide
*/
- // TODO: unhide
@NonNull
public List<Session2Token> getSession2Tokens(int userId) {
try {
@@ -342,13 +338,25 @@
* for consistent behavior across all devices.
*
* @param listener The listener to add
- * @param handler The handler to call listener on. If {@code null}, calling thread's looper will
- * be used.
- * @hide
*/
- // TODO(jaewan): Unhide
public void addOnSession2TokensChangedListener(
- @NonNull OnSession2TokensChangedListener listener, @Nullable Handler handler) {
+ @NonNull OnSession2TokensChangedListener listener) {
+ addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, new Handler());
+ }
+
+ /**
+ * Adds a listener to be notified when the {@link #getSession2Tokens()} changes.
+ * <p>
+ * This API is not generally intended for third party application developers.
+ * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+ * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
+ * for consistent behavior across all devices.
+ *
+ * @param listener The listener to add
+ * @param handler The handler to call listener on.
+ */
+ public void addOnSession2TokensChangedListener(
+ @NonNull OnSession2TokensChangedListener listener, @NonNull Handler handler) {
addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, handler);
}
@@ -392,9 +400,7 @@
* Removes the {@link OnSession2TokensChangedListener} to stop receiving session token updates.
*
* @param listener The listener to remove.
- * @hide
*/
- // TODO(jaewan): Unhide
public void removeOnSession2TokensChangedListener(
@NonNull OnSession2TokensChangedListener listener) {
if (listener == null) {
@@ -688,8 +694,6 @@
* Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
* <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
* for consistent behavior across all devices.
- *
- * @hide
*/
public interface OnSession2TokensChangedListener {
/**
@@ -793,7 +797,6 @@
private final String mPackageName;
private final int mPid;
private final int mUid;
- private final IBinder mCallerBinder;
/**
* Create a new remote user information.
@@ -803,22 +806,9 @@
* @param uid The uid of the remote user
*/
public RemoteUserInfo(@NonNull String packageName, int pid, int uid) {
- this(packageName, pid, uid, null);
- }
-
- /**
- * Create a new remote user information.
- *
- * @param packageName The package name of the remote user
- * @param pid The pid of the remote user
- * @param uid The uid of the remote user
- * @param callerBinder The binder of the remote user. Can be {@code null}.
- */
- public RemoteUserInfo(String packageName, int pid, int uid, IBinder callerBinder) {
mPackageName = packageName;
mPid = pid;
mUid = uid;
- mCallerBinder = callerBinder;
}
/**
@@ -843,13 +833,8 @@
}
/**
- * Returns equality of two RemoteUserInfo. Two RemoteUserInfos are the same only if they're
- * sent to the same controller (either {@link MediaController} or
- * {@link MediaBrowser}. If it's not nor one of them is triggered by the key presses, they
- * would be considered as different one.
- * <p>
- * If you only want to compare the caller's package, compare them with the
- * {@link #getPackageName()}, {@link #getPid()}, and/or {@link #getUid()} directly.
+ * Returns equality of two RemoteUserInfo. Two RemoteUserInfo objects are equal
+ * if and only if they have the same package name, same pid, and same uid.
*
* @param obj the reference object with which to compare.
* @return {@code true} if equals, {@code false} otherwise
@@ -863,8 +848,9 @@
return true;
}
RemoteUserInfo otherUserInfo = (RemoteUserInfo) obj;
- return (mCallerBinder == null || otherUserInfo.mCallerBinder == null) ? false
- : mCallerBinder.equals(otherUserInfo.mCallerBinder);
+ return TextUtils.equals(mPackageName, otherUserInfo.mPackageName)
+ && mPid == otherUserInfo.mPid
+ && mUid == otherUserInfo.mUid;
}
@Override
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 5cb8fb8..30907a5 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -33,7 +33,10 @@
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiUtils;
+import android.hardware.hdmi.HdmiUtils.HdmiAddressRelativePosition;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
@@ -49,7 +52,6 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Retention;
@@ -145,6 +147,8 @@
// Attributes specific to HDMI
private final HdmiDeviceInfo mHdmiDeviceInfo;
private final boolean mIsConnectedToHdmiSwitch;
+ @HdmiAddressRelativePosition
+ private final int mHdmiConnectionRelativePosition;
private final String mParentId;
private final Bundle mExtras;
@@ -260,7 +264,9 @@
private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput,
CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected,
String setupActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo,
- boolean isConnectedToHdmiSwitch, String parentId, Bundle extras) {
+ boolean isConnectedToHdmiSwitch,
+ @HdmiAddressRelativePosition int hdmiConnectionRelativePosition, String parentId,
+ Bundle extras) {
mService = service;
mId = id;
mType = type;
@@ -275,6 +281,7 @@
mTunerCount = tunerCount;
mHdmiDeviceInfo = hdmiDeviceInfo;
mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch;
+ mHdmiConnectionRelativePosition = hdmiConnectionRelativePosition;
mParentId = parentId;
mExtras = extras;
}
@@ -419,6 +426,7 @@
/**
* Returns {@code true}, if a CEC device for this TV input is connected to an HDMI switch, i.e.,
* the device isn't directly connected to a HDMI port.
+ * TODO(b/110094868): add @Deprecated for Q
* @hide
*/
@SystemApi
@@ -427,6 +435,16 @@
}
/**
+ * Returns the relative position of this HDMI input.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ @HdmiAddressRelativePosition
+ public int getHdmiConnectionRelativePosition() {
+ return mHdmiConnectionRelativePosition;
+ }
+
+ /**
* Checks if this TV input is marked hidden by the user in the settings.
*
* @param context Supplies a {@link Context} used to check if this TV input is hidden.
@@ -555,6 +573,7 @@
&& mTunerCount == obj.mTunerCount
&& Objects.equals(mHdmiDeviceInfo, obj.mHdmiDeviceInfo)
&& mIsConnectedToHdmiSwitch == obj.mIsConnectedToHdmiSwitch
+ && mHdmiConnectionRelativePosition == obj.mHdmiConnectionRelativePosition
&& TextUtils.equals(mParentId, obj.mParentId)
&& Objects.equals(mExtras, obj.mExtras);
}
@@ -589,6 +608,7 @@
dest.writeInt(mTunerCount);
dest.writeParcelable(mHdmiDeviceInfo, flags);
dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0);
+ dest.writeInt(mHdmiConnectionRelativePosition);
dest.writeString(mParentId);
dest.writeBundle(mExtras);
}
@@ -630,6 +650,7 @@
mTunerCount = in.readInt();
mHdmiDeviceInfo = in.readParcelable(null);
mIsConnectedToHdmiSwitch = in.readByte() == 1;
+ mHdmiConnectionRelativePosition = in.readInt();
mParentId = in.readString();
mExtras = in.readBundle();
}
@@ -883,12 +904,17 @@
int type;
boolean isHardwareInput = false;
boolean isConnectedToHdmiSwitch = false;
+ @HdmiAddressRelativePosition
+ int hdmiConnectionRelativePosition = HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN;
if (mHdmiDeviceInfo != null) {
id = generateInputId(componentName, mHdmiDeviceInfo);
type = TYPE_HDMI;
isHardwareInput = true;
- isConnectedToHdmiSwitch = (mHdmiDeviceInfo.getPhysicalAddress() & 0x0FFF) != 0;
+ hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo);
+ isConnectedToHdmiSwitch =
+ hdmiConnectionRelativePosition
+ != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW;
} else if (mTvInputHardwareInfo != null) {
id = generateInputId(componentName, mTvInputHardwareInfo);
type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER);
@@ -901,7 +927,8 @@
return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabel, mLabelResId,
mIcon, mIconStandby, mIconDisconnected, mSetupActivity,
mCanRecord == null ? false : mCanRecord, mTunerCount == null ? 0 : mTunerCount,
- mHdmiDeviceInfo, isConnectedToHdmiSwitch, mParentId, mExtras);
+ mHdmiDeviceInfo, isConnectedToHdmiSwitch, hdmiConnectionRelativePosition,
+ mParentId, mExtras);
}
private static String generateInputId(ComponentName name) {
@@ -923,6 +950,16 @@
+ tvInputHardwareInfo.getDeviceId();
}
+ private static int getRelativePosition(Context context, HdmiDeviceInfo info) {
+ HdmiControlManager hcm =
+ (HdmiControlManager) context.getSystemService(Context.HDMI_CONTROL_SERVICE);
+ if (hcm == null) {
+ return HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN;
+ }
+ return HdmiUtils.getHdmiAddressRelativePosition(
+ info.getPhysicalAddress(), hcm.getPhysicalAddress());
+ }
+
private void parseServiceMetadata(int inputType) {
ServiceInfo si = mResolveInfo.serviceInfo;
PackageManager pm = mContext.getPackageManager();
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 42c5b05..81fce8a 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -542,14 +542,15 @@
// static
-bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType) {
+bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType,
+ DrmPlugin::SecurityLevel securityLevel) {
sp<IDrm> drm = MakeDrm();
if (drm == NULL) {
return false;
}
- return drm->isCryptoSchemeSupported(uuid, mimeType);
+ return drm->isCryptoSchemeSupported(uuid, mimeType, securityLevel);
}
status_t JDrm::initCheck() const {
@@ -930,8 +931,30 @@
setDrm(env, thiz, drm);
}
+DrmPlugin::SecurityLevel jintToSecurityLevel(jint jlevel) {
+ DrmPlugin::SecurityLevel level;
+
+ if (jlevel == gSecurityLevels.kSecurityLevelMax) {
+ level = DrmPlugin::kSecurityLevelMax;
+ } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureCrypto) {
+ level = DrmPlugin::kSecurityLevelSwSecureCrypto;
+ } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureDecode) {
+ level = DrmPlugin::kSecurityLevelSwSecureDecode;
+ } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureCrypto) {
+ level = DrmPlugin::kSecurityLevelHwSecureCrypto;
+ } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureDecode) {
+ level = DrmPlugin::kSecurityLevelHwSecureDecode;
+ } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureAll) {
+ level = DrmPlugin::kSecurityLevelHwSecureAll;
+ } else {
+ level = DrmPlugin::kSecurityLevelUnknown;
+ }
+ return level;
+}
+
static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative(
- JNIEnv *env, jobject /* thiz */, jbyteArray uuidObj, jstring jmimeType) {
+ JNIEnv *env, jobject /* thiz */, jbyteArray uuidObj, jstring jmimeType,
+ jint jSecurityLevel) {
if (uuidObj == NULL) {
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
@@ -952,8 +975,9 @@
if (jmimeType != NULL) {
mimeType = JStringToString8(env, jmimeType);
}
+ DrmPlugin::SecurityLevel securityLevel = jintToSecurityLevel(jSecurityLevel);
- return JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType);
+ return JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType, securityLevel);
}
static jbyteArray android_media_MediaDrm_openSession(
@@ -965,21 +989,8 @@
}
Vector<uint8_t> sessionId;
- DrmPlugin::SecurityLevel level;
-
- if (jlevel == gSecurityLevels.kSecurityLevelMax) {
- level = DrmPlugin::kSecurityLevelMax;
- } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureCrypto) {
- level = DrmPlugin::kSecurityLevelSwSecureCrypto;
- } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureDecode) {
- level = DrmPlugin::kSecurityLevelSwSecureDecode;
- } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureCrypto) {
- level = DrmPlugin::kSecurityLevelHwSecureCrypto;
- } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureDecode) {
- level = DrmPlugin::kSecurityLevelHwSecureDecode;
- } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureAll) {
- level = DrmPlugin::kSecurityLevelHwSecureAll;
- } else {
+ DrmPlugin::SecurityLevel level = jintToSecurityLevel(jlevel);
+ if (level == DrmPlugin::kSecurityLevelUnknown) {
jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid security level");
return NULL;
}
@@ -1903,7 +1914,7 @@
{ "native_setup", "(Ljava/lang/Object;[BLjava/lang/String;)V",
(void *)android_media_MediaDrm_native_setup },
- { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;)Z",
+ { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;I)Z",
(void *)android_media_MediaDrm_isCryptoSchemeSupportedNative },
{ "openSession", "(I)[B",
diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h
index b9356f3..9338861 100644
--- a/media/jni/android_media_MediaDrm.h
+++ b/media/jni/android_media_MediaDrm.h
@@ -37,7 +37,9 @@
};
struct JDrm : public BnDrmClient {
- static bool IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType);
+ static bool IsCryptoSchemeSupported(const uint8_t uuid[16],
+ const String8 &mimeType,
+ DrmPlugin::SecurityLevel level);
JDrm(JNIEnv *env, jobject thiz, const uint8_t uuid[16], const String8 &appPackageName);
diff --git a/media/packages/MediaCore/Android.bp b/media/packages/MediaCore/Android.bp.bak
similarity index 100%
rename from media/packages/MediaCore/Android.bp
rename to media/packages/MediaCore/Android.bp.bak
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 73d4c45..7c1af4a 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -56,6 +56,7 @@
shared_libs: [
"liblog",
+ "libhidlbase",
"libcutils",
"libandroidfw",
"libinput",
@@ -70,6 +71,8 @@
"libnetd_client",
"libhwui",
"libxml2",
+ "android.hardware.configstore@1.0",
+ "android.hardware.configstore-utils",
],
static_libs: [
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 8be8eda..51afbc7 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -172,6 +172,7 @@
ASensorEventQueue_hasEvents;
ASensorEventQueue_registerSensor; # introduced=26
ASensorEventQueue_setEventRate;
+ ASensorEventQueue_requestAdditionalInfoEvents; # introduced=29
ASensorManager_configureDirectReport; # introduced=26
ASensorManager_createEventQueue;
ASensorManager_createHardwareBufferDirectChannel; # introduced=26
@@ -185,6 +186,7 @@
ASensorManager_getSensorList;
ASensor_getFifoMaxEventCount; # introduced=21
ASensor_getFifoReservedEventCount; # introduced=21
+ ASensor_getHandle; # introduced=29
ASensor_getHighestDirectReportRateLevel; # introduced=26
ASensor_getMinDelay;
ASensor_getName;
@@ -207,7 +209,7 @@
AStorageManager_unmountObb;
ASurfaceControl_create; # introduced=29
ASurfaceControl_createFromWindow; # introduced=29
- ASurfaceControl_destroy; # introduced=29
+ ASurfaceControl_release; # introduced=29
ASurfaceTexture_acquireANativeWindow; # introduced=28
ASurfaceTexture_attachToGLContext; # introduced=28
ASurfaceTexture_detachFromGLContext; # introduced=28
@@ -216,13 +218,25 @@
ASurfaceTexture_getTransformMatrix; # introduced=28
ASurfaceTexture_release; # introduced=28
ASurfaceTexture_updateTexImage; # introduced=28
+ ASurfaceTransactionStats_getAcquireTime; # introduced=29
+ ASurfaceTransactionStats_getASurfaceControls; # introduced=29
+ ASurfaceTransactionStats_getLatchTime; # introduced=29
+ ASurfaceTransactionStats_getPresentFenceFd; # introduced=29
+ ASurfaceTransactionStats_getPreviousReleaseFenceFd; # introduced=29
+ ASurfaceTransactionStats_releaseASurfaceControls; # introduced=29
ASurfaceTransaction_apply; # introduced=29
ASurfaceTransaction_create; # introduced=29
ASurfaceTransaction_delete; # introduced=29
+ ASurfaceTransaction_reparent; # introduced=29
ASurfaceTransaction_setBuffer; # introduced=29
+ ASurfaceTransaction_setBufferAlpha; # introduced=29
ASurfaceTransaction_setBufferTransparency; # introduced=29
+ ASurfaceTransaction_setColor; # introduced=29
ASurfaceTransaction_setDamageRegion; # introduced=29
+ ASurfaceTransaction_setDesiredPresentTime; # introduced=29
ASurfaceTransaction_setGeometry; # introduced=29
+ ASurfaceTransaction_setHdrMetadata_cta861_3; # introduced=29
+ ASurfaceTransaction_setHdrMetadata_smpte2086; # introduced=29
ASurfaceTransaction_setOnComplete; # introduced=29
ASurfaceTransaction_setVisibility; # introduced=29
ASurfaceTransaction_setZOrder; # introduced=29
diff --git a/native/android/sensor.cpp b/native/android/sensor.cpp
index c3b2e25..63082fd 100644
--- a/native/android/sensor.cpp
+++ b/native/android/sensor.cpp
@@ -115,6 +115,7 @@
if (queue != 0) {
ALooper_addFd(looper, queue->getFd(), ident, ALOOPER_EVENT_INPUT, callback, data);
queue->looper = looper;
+ queue->requestAdditionalInfo = false;
queue->incStrong(manager);
}
return static_cast<ASensorEventQueue*>(queue.get());
@@ -274,11 +275,19 @@
return android::BAD_VALUE;
}
- ssize_t actual = static_cast<SensorEventQueue*>(queue)->read(events, count);
+ SensorEventQueue* sensorQueue = static_cast<SensorEventQueue*>(queue);
+ ssize_t actual = sensorQueue->read(events, count);
if (actual > 0) {
- static_cast<SensorEventQueue*>(queue)->sendAck(events, actual);
+ sensorQueue->sendAck(events, actual);
}
- return actual;
+
+ return sensorQueue->filterEvents(events, actual);
+}
+
+int ASensorEventQueue_requestAdditionalInfoEvents(ASensorEventQueue* queue, bool enable) {
+ RETURN_IF_QUEUE_IS_NULL(android::BAD_VALUE);
+ queue->requestAdditionalInfo = enable;
+ return android::OK;
}
/*****************************************************************************/
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index ead5b0b..5fae9d5 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -14,14 +14,26 @@
* limitations under the License.
*/
+#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
#include <android/native_window.h>
#include <android/surface_control.h>
+#include <configstore/Utils.h>
+
+#include <gui/HdrMetadata.h>
+#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/SurfaceControl.h>
+#include <ui/HdrCapabilities.h>
+
+#include <utils/Timers.h>
+
+using namespace android::hardware::configstore;
+using namespace android::hardware::configstore::V1_0;
using namespace android;
+using android::hardware::configstore::V1_0::ISurfaceFlingerConfigs;
using Transaction = SurfaceComposerClient::Transaction;
@@ -32,6 +44,76 @@
LOG_ALWAYS_FATAL_IF(!static_cast<const Rect&>(name).isValid(), \
"invalid arg passed as " #name " argument");
+static bool getWideColorSupport(const sp<SurfaceControl>& surfaceControl) {
+ sp<SurfaceComposerClient> client = surfaceControl->getClient();
+ sp<IBinder> display(client->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
+
+ Vector<ui::ColorMode> colorModes;
+ status_t err = client->getDisplayColorModes(display, &colorModes);
+ if (err) {
+ ALOGE("unable to get wide color support");
+ return false;
+ }
+
+ bool wideColorBoardConfig =
+ getBool<ISurfaceFlingerConfigs,
+ &ISurfaceFlingerConfigs::hasWideColorDisplay>(false);
+
+ for (android::ui::ColorMode colorMode : colorModes) {
+ switch (colorMode) {
+ case ui::ColorMode::DISPLAY_P3:
+ case ui::ColorMode::ADOBE_RGB:
+ case ui::ColorMode::DCI_P3:
+ if (wideColorBoardConfig) {
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+static bool getHdrSupport(const sp<SurfaceControl>& surfaceControl) {
+ sp<SurfaceComposerClient> client = surfaceControl->getClient();
+ sp<IBinder> display(client->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
+
+ HdrCapabilities hdrCapabilities;
+ status_t err = client->getHdrCapabilities(display, &hdrCapabilities);
+ if (err) {
+ ALOGE("unable to get hdr capabilities");
+ return false;
+ }
+
+ return !hdrCapabilities.getSupportedHdrTypes().empty();
+}
+
+static bool isDataSpaceValid(const sp<SurfaceControl>& surfaceControl, ADataSpace dataSpace) {
+ static_assert(static_cast<int>(ADATASPACE_UNKNOWN) == static_cast<int>(HAL_DATASPACE_UNKNOWN));
+ static_assert(static_cast<int>(ADATASPACE_SCRGB_LINEAR) == static_cast<int>(HAL_DATASPACE_V0_SCRGB_LINEAR));
+ static_assert(static_cast<int>(ADATASPACE_SRGB) == static_cast<int>(HAL_DATASPACE_V0_SRGB));
+ static_assert(static_cast<int>(ADATASPACE_SCRGB) == static_cast<int>(HAL_DATASPACE_V0_SCRGB));
+ static_assert(static_cast<int>(ADATASPACE_DISPLAY_P3) == static_cast<int>(HAL_DATASPACE_DISPLAY_P3));
+ static_assert(static_cast<int>(ADATASPACE_BT2020_PQ) == static_cast<int>(HAL_DATASPACE_BT2020_PQ));
+
+ switch (static_cast<android_dataspace_t>(dataSpace)) {
+ case HAL_DATASPACE_UNKNOWN:
+ case HAL_DATASPACE_V0_SRGB:
+ return true;
+ // These data space need wide gamut support.
+ case HAL_DATASPACE_V0_SCRGB_LINEAR:
+ case HAL_DATASPACE_V0_SCRGB:
+ case HAL_DATASPACE_DISPLAY_P3:
+ return getWideColorSupport(surfaceControl);
+ // These data space need HDR support.
+ case HAL_DATASPACE_BT2020_PQ:
+ return getHdrSupport(surfaceControl);
+ default:
+ return false;
+ }
+}
+
Transaction* ASurfaceTransaction_to_Transaction(ASurfaceTransaction* aSurfaceTransaction) {
return reinterpret_cast<Transaction*>(aSurfaceTransaction);
}
@@ -95,10 +177,9 @@
return reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
}
-void ASurfaceControl_destroy(ASurfaceControl* aSurfaceControl) {
+void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) {
sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
- Transaction().reparent(surfaceControl, nullptr).apply();
SurfaceControl_release(surfaceControl.get());
}
@@ -120,6 +201,86 @@
transaction->apply();
}
+typedef struct ASurfaceControlStats {
+ int64_t acquireTime;
+ sp<Fence> previousReleaseFence;
+} ASurfaceControlStats;
+
+struct ASurfaceTransactionStats {
+ std::unordered_map<ASurfaceControl*, ASurfaceControlStats> aSurfaceControlStats;
+ int64_t latchTime;
+ sp<Fence> presentFence;
+};
+
+int64_t ASurfaceTransactionStats_getLatchTime(ASurfaceTransactionStats* aSurfaceTransactionStats) {
+ CHECK_NOT_NULL(aSurfaceTransactionStats);
+ return aSurfaceTransactionStats->latchTime;
+}
+
+int ASurfaceTransactionStats_getPresentFenceFd(ASurfaceTransactionStats* aSurfaceTransactionStats) {
+ CHECK_NOT_NULL(aSurfaceTransactionStats);
+ auto& presentFence = aSurfaceTransactionStats->presentFence;
+ return (presentFence) ? presentFence->dup() : -1;
+}
+
+void ASurfaceTransactionStats_getASurfaceControls(ASurfaceTransactionStats* aSurfaceTransactionStats,
+ ASurfaceControl*** outASurfaceControls,
+ size_t* outASurfaceControlsSize) {
+ CHECK_NOT_NULL(aSurfaceTransactionStats);
+ CHECK_NOT_NULL(outASurfaceControls);
+ CHECK_NOT_NULL(outASurfaceControlsSize);
+
+ size_t size = aSurfaceTransactionStats->aSurfaceControlStats.size();
+
+ SurfaceControl** surfaceControls = new SurfaceControl*[size];
+ ASurfaceControl** aSurfaceControls = reinterpret_cast<ASurfaceControl**>(surfaceControls);
+
+ size_t i = 0;
+ for (auto& [aSurfaceControl, aSurfaceControlStats] : aSurfaceTransactionStats->aSurfaceControlStats) {
+ aSurfaceControls[i] = aSurfaceControl;
+ i++;
+ }
+
+ *outASurfaceControls = aSurfaceControls;
+ *outASurfaceControlsSize = size;
+}
+
+int64_t ASurfaceTransactionStats_getAcquireTime(ASurfaceTransactionStats* aSurfaceTransactionStats,
+ ASurfaceControl* aSurfaceControl) {
+ CHECK_NOT_NULL(aSurfaceTransactionStats);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ const auto& aSurfaceControlStats =
+ aSurfaceTransactionStats->aSurfaceControlStats.find(aSurfaceControl);
+ LOG_ALWAYS_FATAL_IF(
+ aSurfaceControlStats == aSurfaceTransactionStats->aSurfaceControlStats.end(),
+ "ASurfaceControl not found");
+
+ return aSurfaceControlStats->second.acquireTime;
+}
+
+int ASurfaceTransactionStats_getPreviousReleaseFenceFd(
+ ASurfaceTransactionStats* aSurfaceTransactionStats, ASurfaceControl* aSurfaceControl) {
+ CHECK_NOT_NULL(aSurfaceTransactionStats);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ const auto& aSurfaceControlStats =
+ aSurfaceTransactionStats->aSurfaceControlStats.find(aSurfaceControl);
+ LOG_ALWAYS_FATAL_IF(
+ aSurfaceControlStats == aSurfaceTransactionStats->aSurfaceControlStats.end(),
+ "ASurfaceControl not found");
+
+ auto& previousReleaseFence = aSurfaceControlStats->second.previousReleaseFence;
+ return (previousReleaseFence) ? previousReleaseFence->dup() : -1;
+}
+
+void ASurfaceTransactionStats_releaseASurfaceControls(ASurfaceControl** aSurfaceControls) {
+ CHECK_NOT_NULL(aSurfaceControls);
+
+ SurfaceControl** surfaceControls = reinterpret_cast<SurfaceControl**>(aSurfaceControls);
+ delete[] surfaceControls;
+}
+
void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* aSurfaceTransaction, void* context,
ASurfaceTransaction_OnComplete func) {
CHECK_NOT_NULL(aSurfaceTransaction);
@@ -127,9 +288,23 @@
CHECK_NOT_NULL(func);
TransactionCompletedCallbackTakesContext callback = [func](void* callback_context,
- const TransactionStats& stats) {
- int fence = (stats.presentFence) ? stats.presentFence->dup() : -1;
- (*func)(callback_context, fence);
+ nsecs_t latchTime,
+ const sp<Fence>& presentFence,
+ const std::vector<SurfaceControlStats>& surfaceControlStats) {
+ ASurfaceTransactionStats aSurfaceTransactionStats;
+
+ aSurfaceTransactionStats.latchTime = latchTime;
+ aSurfaceTransactionStats.presentFence = presentFence;
+
+ auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
+
+ for (const auto& [surfaceControl, acquireTime, previousReleaseFence] : surfaceControlStats) {
+ ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
+ aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime;
+ aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence;
+ }
+
+ (*func)(callback_context, &aSurfaceTransactionStats);
};
Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
@@ -137,7 +312,23 @@
transaction->addTransactionCompletedCallback(callback, context);
}
-void ASurfaceTransaction_setVisibility(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
+void ASurfaceTransaction_reparent(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ ASurfaceControl* newParentASurfaceControl) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ sp<SurfaceControl> newParentSurfaceControl = ASurfaceControl_to_SurfaceControl(
+ newParentASurfaceControl);
+ sp<IBinder> newParentHandle = (newParentSurfaceControl)? newParentSurfaceControl->getHandle() : nullptr;
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->reparent(surfaceControl, newParentHandle);
+}
+
+void ASurfaceTransaction_setVisibility(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
int8_t visibility) {
CHECK_NOT_NULL(aSurfaceTransaction);
CHECK_NOT_NULL(aSurfaceControl);
@@ -157,7 +348,8 @@
}
}
-void ASurfaceTransaction_setZOrder(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
+void ASurfaceTransaction_setZOrder(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
int32_t z_order) {
CHECK_NOT_NULL(aSurfaceTransaction);
CHECK_NOT_NULL(aSurfaceControl);
@@ -168,8 +360,9 @@
transaction->setLayer(surfaceControl, z_order);
}
-void ASurfaceTransaction_setBuffer(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
- AHardwareBuffer* buffer, int fence_fd) {
+void ASurfaceTransaction_setBuffer(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ AHardwareBuffer* buffer, int acquire_fence_fd) {
CHECK_NOT_NULL(aSurfaceTransaction);
CHECK_NOT_NULL(aSurfaceControl);
@@ -179,8 +372,8 @@
sp<GraphicBuffer> graphic_buffer(reinterpret_cast<GraphicBuffer*>(buffer));
transaction->setBuffer(surfaceControl, graphic_buffer);
- if (fence_fd != -1) {
- sp<Fence> fence = new Fence(fence_fd);
+ if (acquire_fence_fd != -1) {
+ sp<Fence> fence = new Fence(acquire_fence_fd);
transaction->setAcquireFence(surfaceControl, fence);
}
}
@@ -215,7 +408,8 @@
transaction->setFlags(surfaceControl, flags, layer_state_t::eLayerOpaque);
}
-void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
+void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
const ARect rects[], uint32_t count) {
CHECK_NOT_NULL(aSurfaceTransaction);
CHECK_NOT_NULL(aSurfaceControl);
@@ -230,3 +424,101 @@
transaction->setSurfaceDamageRegion(surfaceControl, region);
}
+
+void ASurfaceTransaction_setDesiredPresentTime(ASurfaceTransaction* aSurfaceTransaction,
+ int64_t desiredPresentTime) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->setDesiredPresentTime(static_cast<nsecs_t>(desiredPresentTime));
+}
+
+void ASurfaceTransaction_setBufferAlpha(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ float alpha) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ LOG_ALWAYS_FATAL_IF(alpha < 0.0 || alpha > 1.0, "invalid alpha");
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->setAlpha(surfaceControl, alpha);
+}
+
+void ASurfaceTransaction_setHdrMetadata_smpte2086(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ struct AHdrMetadata_smpte2086* metadata) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ HdrMetadata hdrMetadata;
+
+ if (metadata) {
+ hdrMetadata.smpte2086.displayPrimaryRed.x = metadata->displayPrimaryRed.x;
+ hdrMetadata.smpte2086.displayPrimaryRed.y = metadata->displayPrimaryRed.y;
+ hdrMetadata.smpte2086.displayPrimaryGreen.x = metadata->displayPrimaryGreen.x;
+ hdrMetadata.smpte2086.displayPrimaryGreen.y = metadata->displayPrimaryGreen.y;
+ hdrMetadata.smpte2086.displayPrimaryBlue.x = metadata->displayPrimaryBlue.x;
+ hdrMetadata.smpte2086.displayPrimaryBlue.y = metadata->displayPrimaryBlue.y;
+ hdrMetadata.smpte2086.whitePoint.x = metadata->whitePoint.x;
+ hdrMetadata.smpte2086.whitePoint.y = metadata->whitePoint.y;
+ hdrMetadata.smpte2086.minLuminance = metadata->minLuminance;
+ hdrMetadata.smpte2086.maxLuminance = metadata->maxLuminance;
+
+ hdrMetadata.validTypes |= HdrMetadata::SMPTE2086;
+ } else {
+ hdrMetadata.validTypes &= ~HdrMetadata::SMPTE2086;
+ }
+
+ transaction->setHdrMetadata(surfaceControl, hdrMetadata);
+}
+
+void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ struct AHdrMetadata_cta861_3* metadata) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ HdrMetadata hdrMetadata;
+
+ if (metadata) {
+ hdrMetadata.cta8613.maxContentLightLevel = metadata->maxContentLightLevel;
+ hdrMetadata.cta8613.maxFrameAverageLightLevel = metadata->maxFrameAverageLightLevel;
+
+ hdrMetadata.validTypes |= HdrMetadata::CTA861_3;
+ } else {
+ hdrMetadata.validTypes &= ~HdrMetadata::CTA861_3;
+ }
+
+ transaction->setHdrMetadata(surfaceControl, hdrMetadata);
+}
+
+void ASurfaceTransaction_setColor(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ float r, float g, float b, float alpha,
+ ADataSpace dataspace) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ LOG_ALWAYS_FATAL_IF(!isDataSpaceValid(surfaceControl, dataspace), "invalid dataspace");
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ half3 color;
+ color.r = r;
+ color.g = g;
+ color.b = b;
+
+ transaction->setColor(surfaceControl, color)
+ .setColorAlpha(surfaceControl, alpha)
+ .setColorDataspace(surfaceControl, static_cast<ui::Dataspace>(dataspace));
+}
diff --git a/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
index 39a1676..406b44d 100644
--- a/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
+++ b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
@@ -20,8 +20,11 @@
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.provider.DeviceConfig;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.KeyValueListParser;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -29,6 +32,7 @@
* Observes the settings for {@link Assistant}.
*/
final class AssistantSettings extends ContentObserver {
+ private static final String LOG_TAG = "AssistantSettings";
public static Factory FACTORY = AssistantSettings::createAndRegister;
private static final boolean DEFAULT_GENERATE_REPLIES = true;
private static final boolean DEFAULT_GENERATE_ACTIONS = true;
@@ -39,19 +43,33 @@
private static final Uri DISMISS_TO_VIEW_RATIO_LIMIT_URI =
Settings.Global.getUriFor(
Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT);
- private static final Uri SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI =
- Settings.Global.getUriFor(
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS);
private static final Uri NOTIFICATION_NEW_INTERRUPTION_MODEL_URI =
Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL);
- private static final String KEY_GENERATE_REPLIES = "generate_replies";
- private static final String KEY_GENERATE_ACTIONS = "generate_actions";
+ /**
+ * Flag determining whether the Notification Assistant should generate replies for
+ * notifications.
+ * <p>
+ * This flag belongs to the namespace: {@link DeviceConfig#NAMESPACE_NOTIFICATION_ASSISTANT}.
+ */
+ @VisibleForTesting
+ static final String KEY_GENERATE_REPLIES = "notification_assistant_generate_replies";
+
+ /**
+ * Flag determining whether the Notification Assistant should generate contextual actions in
+ * notifications.
+ * <p>
+ * This flag belongs to the namespace: {@link DeviceConfig#NAMESPACE_NOTIFICATION_ASSISTANT}.
+ */
+ @VisibleForTesting
+ static final String KEY_GENERATE_ACTIONS = "notification_assistant_generate_actions";
private final KeyValueListParser mParser = new KeyValueListParser(',');
private final ContentResolver mResolver;
private final int mUserId;
+ private final Handler mHandler;
+
@VisibleForTesting
protected final Runnable mOnUpdateRunnable;
@@ -65,6 +83,7 @@
private AssistantSettings(Handler handler, ContentResolver resolver, int userId,
Runnable onUpdateRunnable) {
super(handler);
+ mHandler = handler;
mResolver = resolver;
mUserId = userId;
mOnUpdateRunnable = onUpdateRunnable;
@@ -75,6 +94,7 @@
AssistantSettings assistantSettings =
new AssistantSettings(handler, resolver, userId, onUpdateRunnable);
assistantSettings.register();
+ assistantSettings.registerDeviceConfigs();
return assistantSettings;
}
@@ -91,13 +111,62 @@
mResolver.registerContentObserver(
DISMISS_TO_VIEW_RATIO_LIMIT_URI, false, this, mUserId);
mResolver.registerContentObserver(STREAK_LIMIT_URI, false, this, mUserId);
- mResolver.registerContentObserver(
- SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI, false, this, mUserId);
// Update all uris on creation.
update(null);
}
+ private void registerDeviceConfigs() {
+ DeviceConfig.addOnPropertyChangedListener(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ this::postToHandler,
+ this::onDeviceConfigPropertyChanged);
+
+ // Update the fields in this class from the current state of the device config.
+ updateFromDeviceConfigFlags();
+ }
+
+ private void postToHandler(Runnable r) {
+ this.mHandler.post(r);
+ }
+
+ @VisibleForTesting
+ void onDeviceConfigPropertyChanged(String namespace, String name, String value) {
+ if (!DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT.equals(namespace)) {
+ Log.e(LOG_TAG, "Received update from DeviceConfig for unrelated namespace: "
+ + namespace + " " + name + "=" + value);
+ return;
+ }
+
+ updateFromDeviceConfigFlags();
+ }
+
+ private void updateFromDeviceConfigFlags() {
+ String generateRepliesFlag = DeviceConfig.getProperty(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ KEY_GENERATE_REPLIES);
+ if (TextUtils.isEmpty(generateRepliesFlag)) {
+ mGenerateReplies = DEFAULT_GENERATE_REPLIES;
+ } else {
+ // parseBoolean returns false for everything that isn't 'true' so there's no need to
+ // sanitise the flag string here.
+ mGenerateReplies = Boolean.parseBoolean(generateRepliesFlag);
+ }
+
+ String generateActionsFlag = DeviceConfig.getProperty(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ KEY_GENERATE_ACTIONS);
+ if (TextUtils.isEmpty(generateActionsFlag)) {
+ mGenerateActions = DEFAULT_GENERATE_ACTIONS;
+ } else {
+ // parseBoolean returns false for everything that isn't 'true' so there's no need to
+ // sanitise the flag string here.
+ mGenerateActions = Boolean.parseBoolean(generateActionsFlag);
+ }
+
+ mOnUpdateRunnable.run();
+ }
+
@Override
public void onChange(boolean selfChange, Uri uri) {
update(uri);
@@ -114,15 +183,6 @@
mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
ChannelImpressions.DEFAULT_STREAK_LIMIT);
}
- if (uri == null || SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI.equals(uri)) {
- mParser.setString(
- Settings.Global.getString(mResolver,
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
- mGenerateReplies =
- mParser.getBoolean(KEY_GENERATE_REPLIES, DEFAULT_GENERATE_REPLIES);
- mGenerateActions =
- mParser.getBoolean(KEY_GENERATE_ACTIONS, DEFAULT_GENERATE_ACTIONS);
- }
if (uri == null || NOTIFICATION_NEW_INTERRUPTION_MODEL_URI.equals(uri)) {
int mNewInterruptionModelInt = Settings.Secure.getInt(
mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL,
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
index fd23f2b..51b723d 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
@@ -26,6 +26,7 @@
import android.content.ContentResolver;
import android.os.Handler;
import android.os.Looper;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
@@ -62,9 +63,6 @@
Settings.Global.putFloat(mResolver,
Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, 0.8f);
Settings.Global.putInt(mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, 2);
- Settings.Global.putString(mResolver,
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
- "generate_replies=true,generate_actions=true");
Settings.Secure.putInt(mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1);
mAssistantSettings = AssistantSettings.createForTesting(
@@ -73,56 +71,78 @@
@Test
public void testGenerateRepliesDisabled() {
- Settings.Global.putString(mResolver,
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
- "generate_replies=false");
-
- // Notify for the settings values we updated.
- mAssistantSettings.onChange(false,
- Settings.Global.getUriFor(
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
-
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_REPLIES,
+ "false");
assertFalse(mAssistantSettings.mGenerateReplies);
}
@Test
public void testGenerateRepliesEnabled() {
- Settings.Global.putString(mResolver,
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_replies=true");
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_REPLIES,
+ "true");
- // Notify for the settings values we updated.
- mAssistantSettings.onChange(false,
- Settings.Global.getUriFor(
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+ assertTrue(mAssistantSettings.mGenerateReplies);
+ }
+ @Test
+ public void testGenerateRepliesEmptyFlag() {
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_REPLIES,
+ "false");
+
+ assertFalse(mAssistantSettings.mGenerateReplies);
+
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_REPLIES,
+ "");
+
+ // Go back to the default value.
assertTrue(mAssistantSettings.mGenerateReplies);
}
@Test
public void testGenerateActionsDisabled() {
- Settings.Global.putString(mResolver,
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=false");
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_ACTIONS,
+ "false");
- // Notify for the settings values we updated.
- mAssistantSettings.onChange(false,
- Settings.Global.getUriFor(
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
-
- assertTrue(mAssistantSettings.mGenerateReplies);
+ assertFalse(mAssistantSettings.mGenerateActions);
}
@Test
public void testGenerateActionsEnabled() {
- Settings.Global.putString(mResolver,
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=true");
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_ACTIONS,
+ "true");
- // Notify for the settings values we updated.
- mAssistantSettings.onChange(false,
- Settings.Global.getUriFor(
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+ assertTrue(mAssistantSettings.mGenerateActions);
+ }
- assertTrue(mAssistantSettings.mGenerateReplies);
+ @Test
+ public void testGenerateActionsEmptyFlag() {
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_ACTIONS,
+ "false");
+
+ assertFalse(mAssistantSettings.mGenerateActions);
+
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_ACTIONS,
+ "");
+
+ // Go back to the default value.
+ assertTrue(mAssistantSettings.mGenerateActions);
}
@Test
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 8d04702..da3416b 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -76,7 +76,7 @@
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
- Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES,
+ Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES, Root.COLUMN_QUERY_ARGS
};
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
@@ -444,6 +444,7 @@
row.add(Root.COLUMN_FLAGS, root.flags);
row.add(Root.COLUMN_TITLE, root.title);
row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
+ row.add(Root.COLUMN_QUERY_ARGS, SUPPORTED_QUERY_ARGS);
long availableBytes = -1;
if (root.reportAvailableBytes) {
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index 2f7d599..a2da0a0 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -24,7 +24,7 @@
":services-networkstack-shared-srcs",
],
static_libs: [
- "dhcp-packet-lib",
+ "services-netlink-lib",
]
}
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
index 7f8bb93..5ab833b 100644
--- a/packages/NetworkStack/AndroidManifest.xml
+++ b/packages/NetworkStack/AndroidManifest.xml
@@ -28,6 +28,7 @@
<!-- Launch captive portal app as specific user -->
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.NETWORK_STACK" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:label="NetworkStack"
android:defaultToDeviceProtectedStorage="true"
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
similarity index 98%
rename from services/net/java/android/net/apf/ApfFilter.java
rename to packages/NetworkStack/src/android/net/apf/ApfFilter.java
index 3351b25..50c4dfc 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
@@ -16,10 +16,7 @@
package android.net.apf;
-import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE;
-import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION;
+import static android.net.util.SocketUtils.makePacketSocketAddress;
import static android.system.OsConstants.AF_PACKET;
import static android.system.OsConstants.ARPHRD_ETHER;
import static android.system.OsConstants.ETH_P_ARP;
@@ -34,6 +31,10 @@
import static com.android.internal.util.BitUtils.getUint32;
import static com.android.internal.util.BitUtils.getUint8;
import static com.android.internal.util.BitUtils.uint32;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -45,7 +46,7 @@
import android.net.NetworkUtils;
import android.net.apf.ApfGenerator.IllegalInstructionException;
import android.net.apf.ApfGenerator.Register;
-import android.net.ip.IpClientCallbacks;
+import android.net.ip.IpClient.IpClientCallbacksWrapper;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
import android.net.metrics.IpConnectivityLog;
@@ -55,7 +56,6 @@
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
-import android.system.PacketSocketAddress;
import android.text.format.DateUtils;
import android.util.Log;
import android.util.Pair;
@@ -72,6 +72,7 @@
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
+import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.BufferUnderflowException;
@@ -336,7 +337,7 @@
private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
private final ApfCapabilities mApfCapabilities;
- private final IpClientCallbacks mIpClientCallback;
+ private final IpClientCallbacksWrapper mIpClientCallback;
private final InterfaceParams mInterfaceParams;
private final IpConnectivityLog mMetricsLog;
@@ -377,7 +378,7 @@
@VisibleForTesting
ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams,
- IpClientCallbacks ipClientCallback, IpConnectivityLog log) {
+ IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) {
mApfCapabilities = config.apfCapabilities;
mIpClientCallback = ipClientCallback;
mInterfaceParams = ifParams;
@@ -472,7 +473,7 @@
installNewProgramLocked();
}
socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6);
- PacketSocketAddress addr = new PacketSocketAddress(
+ SocketAddress addr = makePacketSocketAddress(
(short) ETH_P_IPV6, mInterfaceParams.index);
Os.bind(socket, addr);
NetworkUtils.attachRaFilter(socket, mApfCapabilities.apfPacketFormat);
@@ -1419,7 +1420,7 @@
* filtering using APF programs.
*/
public static ApfFilter maybeCreate(Context context, ApfConfiguration config,
- InterfaceParams ifParams, IpClientCallbacks ipClientCallback) {
+ InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback) {
if (context == null || config == null || ifParams == null) return null;
ApfCapabilities apfCapabilities = config.apfCapabilities;
if (apfCapabilities == null) return null;
diff --git a/services/net/java/android/net/apf/ApfGenerator.java b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java
similarity index 100%
rename from services/net/java/android/net/apf/ApfGenerator.java
rename to packages/NetworkStack/src/android/net/apf/ApfGenerator.java
diff --git a/services/net/java/android/net/dhcp/DhcpAckPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpAckPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
new file mode 100644
index 0000000..04ac9a3
--- /dev/null
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
@@ -0,0 +1,1052 @@
+/*
+ * Copyright (C) 2015 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.net.dhcp;
+
+import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS;
+import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER;
+import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME;
+import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME;
+import static android.net.dhcp.DhcpPacket.DHCP_MTU;
+import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME;
+import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME;
+import static android.net.dhcp.DhcpPacket.DHCP_ROUTER;
+import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK;
+import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO;
+import static android.net.dhcp.DhcpPacket.INADDR_ANY;
+import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
+import static android.net.util.SocketUtils.makePacketSocketAddress;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_PACKET;
+import static android.system.OsConstants.ETH_P_IP;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_RAW;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_BROADCAST;
+import static android.system.OsConstants.SO_RCVBUF;
+import static android.system.OsConstants.SO_REUSEADDR;
+
+import android.content.Context;
+import android.net.DhcpResults;
+import android.net.NetworkUtils;
+import android.net.TrafficStats;
+import android.net.ip.IpClient;
+import android.net.metrics.DhcpClientEvent;
+import android.net.metrics.DhcpErrorEvent;
+import android.net.metrics.IpConnectivityLog;
+import android.net.util.InterfaceParams;
+import android.net.util.SocketUtils;
+import android.os.Message;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.util.HexDump;
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
+
+import libcore.io.IoBridge;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * A DHCPv4 client.
+ *
+ * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android
+ * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine.
+ *
+ * TODO:
+ *
+ * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour).
+ * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not
+ * do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a
+ * given SSID), it requests the last-leased IP address on the same interface, causing a delay if
+ * the server NAKs or a timeout if it doesn't.
+ *
+ * Known differences from current behaviour:
+ *
+ * - Does not request the "static routes" option.
+ * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now.
+ * - Requests the "broadcast" option, but does nothing with it.
+ * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0).
+ *
+ * @hide
+ */
+public class DhcpClient extends StateMachine {
+
+ private static final String TAG = "DhcpClient";
+ private static final boolean DBG = true;
+ private static final boolean STATE_DBG = false;
+ private static final boolean MSG_DBG = false;
+ private static final boolean PACKET_DBG = false;
+
+ // Timers and timeouts.
+ private static final int SECONDS = 1000;
+ private static final int FIRST_TIMEOUT_MS = 2 * SECONDS;
+ private static final int MAX_TIMEOUT_MS = 128 * SECONDS;
+
+ // This is not strictly needed, since the client is asynchronous and implements exponential
+ // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was
+ // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at
+ // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter.
+ private static final int DHCP_TIMEOUT_MS = 36 * SECONDS;
+
+ // DhcpClient uses IpClient's handler.
+ private static final int PUBLIC_BASE = IpClient.DHCPCLIENT_CMD_BASE;
+
+ /* Commands from controller to start/stop DHCP */
+ public static final int CMD_START_DHCP = PUBLIC_BASE + 1;
+ public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2;
+
+ /* Notification from DHCP state machine prior to DHCP discovery/renewal */
+ public static final int CMD_PRE_DHCP_ACTION = PUBLIC_BASE + 3;
+ /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
+ * success/failure */
+ public static final int CMD_POST_DHCP_ACTION = PUBLIC_BASE + 4;
+ /* Notification from DHCP state machine before quitting */
+ public static final int CMD_ON_QUIT = PUBLIC_BASE + 5;
+
+ /* Command from controller to indicate DHCP discovery/renewal can continue
+ * after pre DHCP action is complete */
+ public static final int CMD_PRE_DHCP_ACTION_COMPLETE = PUBLIC_BASE + 6;
+
+ /* Command and event notification to/from IpManager requesting the setting
+ * (or clearing) of an IPv4 LinkAddress.
+ */
+ public static final int CMD_CLEAR_LINKADDRESS = PUBLIC_BASE + 7;
+ public static final int CMD_CONFIGURE_LINKADDRESS = PUBLIC_BASE + 8;
+ public static final int EVENT_LINKADDRESS_CONFIGURED = PUBLIC_BASE + 9;
+
+ /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */
+ public static final int DHCP_SUCCESS = 1;
+ public static final int DHCP_FAILURE = 2;
+
+ // Internal messages.
+ private static final int PRIVATE_BASE = IpClient.DHCPCLIENT_CMD_BASE + 100;
+ private static final int CMD_KICK = PRIVATE_BASE + 1;
+ private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 2;
+ private static final int CMD_TIMEOUT = PRIVATE_BASE + 3;
+ private static final int CMD_RENEW_DHCP = PRIVATE_BASE + 4;
+ private static final int CMD_REBIND_DHCP = PRIVATE_BASE + 5;
+ private static final int CMD_EXPIRE_DHCP = PRIVATE_BASE + 6;
+
+ // For message logging.
+ private static final Class[] sMessageClasses = { DhcpClient.class };
+ private static final SparseArray<String> sMessageNames =
+ MessageUtils.findMessageNames(sMessageClasses);
+
+ // DHCP parameters that we request.
+ /* package */ static final byte[] REQUESTED_PARAMS = new byte[] {
+ DHCP_SUBNET_MASK,
+ DHCP_ROUTER,
+ DHCP_DNS_SERVER,
+ DHCP_DOMAIN_NAME,
+ DHCP_MTU,
+ DHCP_BROADCAST_ADDRESS, // TODO: currently ignored.
+ DHCP_LEASE_TIME,
+ DHCP_RENEWAL_TIME,
+ DHCP_REBINDING_TIME,
+ DHCP_VENDOR_INFO,
+ };
+
+ // DHCP flag that means "yes, we support unicast."
+ private static final boolean DO_UNICAST = false;
+
+ // System services / libraries we use.
+ private final Context mContext;
+ private final Random mRandom;
+ private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+
+ // Sockets.
+ // - We use a packet socket to receive, because servers send us packets bound for IP addresses
+ // which we have not yet configured, and the kernel protocol stack drops these.
+ // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
+ // be off-link as well as on-link).
+ private FileDescriptor mPacketSock;
+ private FileDescriptor mUdpSock;
+ private ReceiveThread mReceiveThread;
+
+ // State variables.
+ private final StateMachine mController;
+ private final WakeupMessage mKickAlarm;
+ private final WakeupMessage mTimeoutAlarm;
+ private final WakeupMessage mRenewAlarm;
+ private final WakeupMessage mRebindAlarm;
+ private final WakeupMessage mExpiryAlarm;
+ private final String mIfaceName;
+
+ private boolean mRegisteredForPreDhcpNotification;
+ private InterfaceParams mIface;
+ // TODO: MacAddress-ify more of this class hierarchy.
+ private byte[] mHwAddr;
+ private SocketAddress mInterfaceBroadcastAddr;
+ private int mTransactionId;
+ private long mTransactionStartMillis;
+ private DhcpResults mDhcpLease;
+ private long mDhcpLeaseExpiry;
+ private DhcpResults mOffer;
+
+ // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState.
+ private long mLastInitEnterTime;
+ private long mLastBoundExitTime;
+
+ // States.
+ private State mStoppedState = new StoppedState();
+ private State mDhcpState = new DhcpState();
+ private State mDhcpInitState = new DhcpInitState();
+ private State mDhcpSelectingState = new DhcpSelectingState();
+ private State mDhcpRequestingState = new DhcpRequestingState();
+ private State mDhcpHaveLeaseState = new DhcpHaveLeaseState();
+ private State mConfiguringInterfaceState = new ConfiguringInterfaceState();
+ private State mDhcpBoundState = new DhcpBoundState();
+ private State mDhcpRenewingState = new DhcpRenewingState();
+ private State mDhcpRebindingState = new DhcpRebindingState();
+ private State mDhcpInitRebootState = new DhcpInitRebootState();
+ private State mDhcpRebootingState = new DhcpRebootingState();
+ private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState);
+ private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState);
+
+ private WakeupMessage makeWakeupMessage(String cmdName, int cmd) {
+ cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName;
+ return new WakeupMessage(mContext, getHandler(), cmdName, cmd);
+ }
+
+ // TODO: Take an InterfaceParams instance instead of an interface name String.
+ private DhcpClient(Context context, StateMachine controller, String iface) {
+ super(TAG, controller.getHandler());
+
+ mContext = context;
+ mController = controller;
+ mIfaceName = iface;
+
+ addState(mStoppedState);
+ addState(mDhcpState);
+ addState(mDhcpInitState, mDhcpState);
+ addState(mWaitBeforeStartState, mDhcpState);
+ addState(mDhcpSelectingState, mDhcpState);
+ addState(mDhcpRequestingState, mDhcpState);
+ addState(mDhcpHaveLeaseState, mDhcpState);
+ addState(mConfiguringInterfaceState, mDhcpHaveLeaseState);
+ addState(mDhcpBoundState, mDhcpHaveLeaseState);
+ addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState);
+ addState(mDhcpRenewingState, mDhcpHaveLeaseState);
+ addState(mDhcpRebindingState, mDhcpHaveLeaseState);
+ addState(mDhcpInitRebootState, mDhcpState);
+ addState(mDhcpRebootingState, mDhcpState);
+
+ setInitialState(mStoppedState);
+
+ mRandom = new Random();
+
+ // Used to schedule packet retransmissions.
+ mKickAlarm = makeWakeupMessage("KICK", CMD_KICK);
+ // Used to time out PacketRetransmittingStates.
+ mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT);
+ // Used to schedule DHCP reacquisition.
+ mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP);
+ mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP);
+ mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP);
+ }
+
+ public void registerForPreDhcpNotification() {
+ mRegisteredForPreDhcpNotification = true;
+ }
+
+ public static DhcpClient makeDhcpClient(
+ Context context, StateMachine controller, InterfaceParams ifParams) {
+ DhcpClient client = new DhcpClient(context, controller, ifParams.name);
+ client.mIface = ifParams;
+ client.start();
+ return client;
+ }
+
+ private boolean initInterface() {
+ if (mIface == null) mIface = InterfaceParams.getByName(mIfaceName);
+ if (mIface == null) {
+ Log.e(TAG, "Can't determine InterfaceParams for " + mIfaceName);
+ return false;
+ }
+
+ mHwAddr = mIface.macAddr.toByteArray();
+ mInterfaceBroadcastAddr = makePacketSocketAddress(mIface.index, DhcpPacket.ETHER_BROADCAST);
+ return true;
+ }
+
+ private void startNewTransaction() {
+ mTransactionId = mRandom.nextInt();
+ mTransactionStartMillis = SystemClock.elapsedRealtime();
+ }
+
+ private boolean initSockets() {
+ return initPacketSocket() && initUdpSocket();
+ }
+
+ private boolean initPacketSocket() {
+ try {
+ mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP);
+ SocketAddress addr = makePacketSocketAddress((short) ETH_P_IP, mIface.index);
+ Os.bind(mPacketSock, addr);
+ NetworkUtils.attachDhcpFilter(mPacketSock);
+ } catch(SocketException|ErrnoException e) {
+ Log.e(TAG, "Error creating packet socket", e);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean initUdpSocket() {
+ final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_DHCP);
+ try {
+ mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ SocketUtils.bindSocketToInterface(mUdpSock, mIfaceName);
+ Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
+ Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
+ Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0);
+ Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT);
+ } catch(SocketException|ErrnoException e) {
+ Log.e(TAG, "Error creating UDP socket", e);
+ return false;
+ } finally {
+ TrafficStats.setThreadStatsTag(oldTag);
+ }
+ return true;
+ }
+
+ private boolean connectUdpSock(Inet4Address to) {
+ try {
+ Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER);
+ return true;
+ } catch (SocketException|ErrnoException e) {
+ Log.e(TAG, "Error connecting UDP socket", e);
+ return false;
+ }
+ }
+
+ private static void closeQuietly(FileDescriptor fd) {
+ try {
+ IoBridge.closeAndSignalBlockedThreads(fd);
+ } catch (IOException ignored) {}
+ }
+
+ private void closeSockets() {
+ closeQuietly(mUdpSock);
+ closeQuietly(mPacketSock);
+ }
+
+ class ReceiveThread extends Thread {
+
+ private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH];
+ private volatile boolean mStopped = false;
+
+ public void halt() {
+ mStopped = true;
+ closeSockets(); // Interrupts the read() call the thread is blocked in.
+ }
+
+ @Override
+ public void run() {
+ if (DBG) Log.d(TAG, "Receive thread started");
+ while (!mStopped) {
+ int length = 0; // Or compiler can't tell it's initialized if a parse error occurs.
+ try {
+ length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
+ DhcpPacket packet = null;
+ packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
+ if (DBG) Log.d(TAG, "Received packet: " + packet);
+ sendMessage(CMD_RECEIVED_PACKET, packet);
+ } catch (IOException|ErrnoException e) {
+ if (!mStopped) {
+ Log.e(TAG, "Read error", e);
+ logError(DhcpErrorEvent.RECEIVE_ERROR);
+ }
+ } catch (DhcpPacket.ParseException e) {
+ Log.e(TAG, "Can't parse packet: " + e.getMessage());
+ if (PACKET_DBG) {
+ Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length));
+ }
+ if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) {
+ int snetTagId = 0x534e4554;
+ String bugId = "31850211";
+ int uid = -1;
+ String data = DhcpPacket.ParseException.class.getName();
+ EventLog.writeEvent(snetTagId, bugId, uid, data);
+ }
+ logError(e.errorCode);
+ }
+ }
+ if (DBG) Log.d(TAG, "Receive thread stopped");
+ }
+ }
+
+ private short getSecs() {
+ return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000);
+ }
+
+ private boolean transmitPacket(ByteBuffer buf, String description, int encap, Inet4Address to) {
+ try {
+ if (encap == DhcpPacket.ENCAP_L2) {
+ if (DBG) Log.d(TAG, "Broadcasting " + description);
+ Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
+ } else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) {
+ if (DBG) Log.d(TAG, "Broadcasting " + description);
+ // We only send L3-encapped broadcasts in DhcpRebindingState,
+ // where we have an IP address and an unconnected UDP socket.
+ //
+ // N.B.: We only need this codepath because DhcpRequestPacket
+ // hardcodes the source IP address to 0.0.0.0. We could reuse
+ // the packet socket if this ever changes.
+ Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER);
+ } else {
+ // It's safe to call getpeername here, because we only send unicast packets if we
+ // have an IP address, and we connect the UDP socket in DhcpBoundState#enter.
+ if (DBG) Log.d(TAG, String.format("Unicasting %s to %s",
+ description, Os.getpeername(mUdpSock)));
+ Os.write(mUdpSock, buf);
+ }
+ } catch(ErrnoException|IOException e) {
+ Log.e(TAG, "Can't send packet: ", e);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean sendDiscoverPacket() {
+ ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
+ DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
+ DO_UNICAST, REQUESTED_PARAMS);
+ return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
+ }
+
+ private boolean sendRequestPacket(
+ Inet4Address clientAddress, Inet4Address requestedAddress,
+ Inet4Address serverAddress, Inet4Address to) {
+ // TODO: should we use the transaction ID from the server?
+ final int encap = INADDR_ANY.equals(clientAddress)
+ ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP;
+
+ ByteBuffer packet = DhcpPacket.buildRequestPacket(
+ encap, mTransactionId, getSecs(), clientAddress,
+ DO_UNICAST, mHwAddr, requestedAddress,
+ serverAddress, REQUESTED_PARAMS, null);
+ String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null;
+ String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
+ " request=" + requestedAddress.getHostAddress() +
+ " serverid=" + serverStr;
+ return transmitPacket(packet, description, encap, to);
+ }
+
+ private void scheduleLeaseTimers() {
+ if (mDhcpLeaseExpiry == 0) {
+ Log.d(TAG, "Infinite lease, no timer scheduling needed");
+ return;
+ }
+
+ final long now = SystemClock.elapsedRealtime();
+
+ // TODO: consider getting the renew and rebind timers from T1 and T2.
+ // See also:
+ // https://tools.ietf.org/html/rfc2131#section-4.4.5
+ // https://tools.ietf.org/html/rfc1533#section-9.9
+ // https://tools.ietf.org/html/rfc1533#section-9.10
+ final long remainingDelay = mDhcpLeaseExpiry - now;
+ final long renewDelay = remainingDelay / 2;
+ final long rebindDelay = remainingDelay * 7 / 8;
+ mRenewAlarm.schedule(now + renewDelay);
+ mRebindAlarm.schedule(now + rebindDelay);
+ mExpiryAlarm.schedule(now + remainingDelay);
+ Log.d(TAG, "Scheduling renewal in " + (renewDelay / 1000) + "s");
+ Log.d(TAG, "Scheduling rebind in " + (rebindDelay / 1000) + "s");
+ Log.d(TAG, "Scheduling expiry in " + (remainingDelay / 1000) + "s");
+ }
+
+ private void notifySuccess() {
+ mController.sendMessage(
+ CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease));
+ }
+
+ private void notifyFailure() {
+ mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0, null);
+ }
+
+ private void acceptDhcpResults(DhcpResults results, String msg) {
+ mDhcpLease = results;
+ mOffer = null;
+ Log.d(TAG, msg + " lease: " + mDhcpLease);
+ notifySuccess();
+ }
+
+ private void clearDhcpState() {
+ mDhcpLease = null;
+ mDhcpLeaseExpiry = 0;
+ mOffer = null;
+ }
+
+ /**
+ * Quit the DhcpStateMachine.
+ *
+ * @hide
+ */
+ public void doQuit() {
+ Log.d(TAG, "doQuit");
+ quit();
+ }
+
+ @Override
+ protected void onQuitting() {
+ Log.d(TAG, "onQuitting");
+ mController.sendMessage(CMD_ON_QUIT);
+ }
+
+ abstract class LoggingState extends State {
+ private long mEnterTimeMs;
+
+ @Override
+ public void enter() {
+ if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
+ mEnterTimeMs = SystemClock.elapsedRealtime();
+ }
+
+ @Override
+ public void exit() {
+ long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs;
+ logState(getName(), (int) durationMs);
+ }
+
+ private String messageName(int what) {
+ return sMessageNames.get(what, Integer.toString(what));
+ }
+
+ private String messageToString(Message message) {
+ long now = SystemClock.uptimeMillis();
+ return new StringBuilder(" ")
+ .append(message.getWhen() - now)
+ .append(messageName(message.what))
+ .append(" ").append(message.arg1)
+ .append(" ").append(message.arg2)
+ .append(" ").append(message.obj)
+ .toString();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (MSG_DBG) {
+ Log.d(TAG, getName() + messageToString(message));
+ }
+ return NOT_HANDLED;
+ }
+
+ @Override
+ public String getName() {
+ // All DhcpClient's states are inner classes with a well defined name.
+ // Use getSimpleName() and avoid super's getName() creating new String instances.
+ return getClass().getSimpleName();
+ }
+ }
+
+ // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with
+ // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState.
+ abstract class WaitBeforeOtherState extends LoggingState {
+ protected State mOtherState;
+
+ @Override
+ public void enter() {
+ super.enter();
+ mController.sendMessage(CMD_PRE_DHCP_ACTION);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case CMD_PRE_DHCP_ACTION_COMPLETE:
+ transitionTo(mOtherState);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class StoppedState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_START_DHCP:
+ if (mRegisteredForPreDhcpNotification) {
+ transitionTo(mWaitBeforeStartState);
+ } else {
+ transitionTo(mDhcpInitState);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class WaitBeforeStartState extends WaitBeforeOtherState {
+ public WaitBeforeStartState(State otherState) {
+ super();
+ mOtherState = otherState;
+ }
+ }
+
+ class WaitBeforeRenewalState extends WaitBeforeOtherState {
+ public WaitBeforeRenewalState(State otherState) {
+ super();
+ mOtherState = otherState;
+ }
+ }
+
+ class DhcpState extends State {
+ @Override
+ public void enter() {
+ clearDhcpState();
+ if (initInterface() && initSockets()) {
+ mReceiveThread = new ReceiveThread();
+ mReceiveThread.start();
+ } else {
+ notifyFailure();
+ transitionTo(mStoppedState);
+ }
+ }
+
+ @Override
+ public void exit() {
+ if (mReceiveThread != null) {
+ mReceiveThread.halt(); // Also closes sockets.
+ mReceiveThread = null;
+ }
+ clearDhcpState();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case CMD_STOP_DHCP:
+ transitionTo(mStoppedState);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ public boolean isValidPacket(DhcpPacket packet) {
+ // TODO: check checksum.
+ int xid = packet.getTransactionId();
+ if (xid != mTransactionId) {
+ Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId);
+ return false;
+ }
+ if (!Arrays.equals(packet.getClientMac(), mHwAddr)) {
+ Log.d(TAG, "MAC addr mismatch: got " +
+ HexDump.toHexString(packet.getClientMac()) + ", expected " +
+ HexDump.toHexString(packet.getClientMac()));
+ return false;
+ }
+ return true;
+ }
+
+ public void setDhcpLeaseExpiry(DhcpPacket packet) {
+ long leaseTimeMillis = packet.getLeaseTimeMillis();
+ mDhcpLeaseExpiry =
+ (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0;
+ }
+
+ /**
+ * Retransmits packets using jittered exponential backoff with an optional timeout. Packet
+ * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass
+ * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout
+ * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the
+ * state.
+ *
+ * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a
+ * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET
+ * sent by the receive thread. They may also set mTimeout and implement timeout.
+ */
+ abstract class PacketRetransmittingState extends LoggingState {
+
+ private int mTimer;
+ protected int mTimeout = 0;
+
+ @Override
+ public void enter() {
+ super.enter();
+ initTimer();
+ maybeInitTimeout();
+ sendMessage(CMD_KICK);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case CMD_KICK:
+ sendPacket();
+ scheduleKick();
+ return HANDLED;
+ case CMD_RECEIVED_PACKET:
+ receivePacket((DhcpPacket) message.obj);
+ return HANDLED;
+ case CMD_TIMEOUT:
+ timeout();
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+
+ @Override
+ public void exit() {
+ super.exit();
+ mKickAlarm.cancel();
+ mTimeoutAlarm.cancel();
+ }
+
+ abstract protected boolean sendPacket();
+ abstract protected void receivePacket(DhcpPacket packet);
+ protected void timeout() {}
+
+ protected void initTimer() {
+ mTimer = FIRST_TIMEOUT_MS;
+ }
+
+ protected int jitterTimer(int baseTimer) {
+ int maxJitter = baseTimer / 10;
+ int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter;
+ return baseTimer + jitter;
+ }
+
+ protected void scheduleKick() {
+ long now = SystemClock.elapsedRealtime();
+ long timeout = jitterTimer(mTimer);
+ long alarmTime = now + timeout;
+ mKickAlarm.schedule(alarmTime);
+ mTimer *= 2;
+ if (mTimer > MAX_TIMEOUT_MS) {
+ mTimer = MAX_TIMEOUT_MS;
+ }
+ }
+
+ protected void maybeInitTimeout() {
+ if (mTimeout > 0) {
+ long alarmTime = SystemClock.elapsedRealtime() + mTimeout;
+ mTimeoutAlarm.schedule(alarmTime);
+ }
+ }
+ }
+
+ class DhcpInitState extends PacketRetransmittingState {
+ public DhcpInitState() {
+ super();
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ startNewTransaction();
+ mLastInitEnterTime = SystemClock.elapsedRealtime();
+ }
+
+ protected boolean sendPacket() {
+ return sendDiscoverPacket();
+ }
+
+ protected void receivePacket(DhcpPacket packet) {
+ if (!isValidPacket(packet)) return;
+ if (!(packet instanceof DhcpOfferPacket)) return;
+ mOffer = packet.toDhcpResults();
+ if (mOffer != null) {
+ Log.d(TAG, "Got pending lease: " + mOffer);
+ transitionTo(mDhcpRequestingState);
+ }
+ }
+ }
+
+ // Not implemented. We request the first offer we receive.
+ class DhcpSelectingState extends LoggingState {
+ }
+
+ class DhcpRequestingState extends PacketRetransmittingState {
+ public DhcpRequestingState() {
+ mTimeout = DHCP_TIMEOUT_MS / 2;
+ }
+
+ protected boolean sendPacket() {
+ return sendRequestPacket(
+ INADDR_ANY, // ciaddr
+ (Inet4Address) mOffer.ipAddress.getAddress(), // DHCP_REQUESTED_IP
+ (Inet4Address) mOffer.serverAddress, // DHCP_SERVER_IDENTIFIER
+ INADDR_BROADCAST); // packet destination address
+ }
+
+ protected void receivePacket(DhcpPacket packet) {
+ if (!isValidPacket(packet)) return;
+ if ((packet instanceof DhcpAckPacket)) {
+ DhcpResults results = packet.toDhcpResults();
+ if (results != null) {
+ setDhcpLeaseExpiry(packet);
+ acceptDhcpResults(results, "Confirmed");
+ transitionTo(mConfiguringInterfaceState);
+ }
+ } else if (packet instanceof DhcpNakPacket) {
+ // TODO: Wait a while before returning into INIT state.
+ Log.d(TAG, "Received NAK, returning to INIT");
+ mOffer = null;
+ transitionTo(mDhcpInitState);
+ }
+ }
+
+ @Override
+ protected void timeout() {
+ // After sending REQUESTs unsuccessfully for a while, go back to init.
+ transitionTo(mDhcpInitState);
+ }
+ }
+
+ class DhcpHaveLeaseState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_EXPIRE_DHCP:
+ Log.d(TAG, "Lease expired!");
+ notifyFailure();
+ transitionTo(mDhcpInitState);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+
+ @Override
+ public void exit() {
+ // Clear any extant alarms.
+ mRenewAlarm.cancel();
+ mRebindAlarm.cancel();
+ mExpiryAlarm.cancel();
+ clearDhcpState();
+ // Tell IpManager to clear the IPv4 address. There is no need to
+ // wait for confirmation since any subsequent packets are sent from
+ // INADDR_ANY anyway (DISCOVER, REQUEST).
+ mController.sendMessage(CMD_CLEAR_LINKADDRESS);
+ }
+ }
+
+ class ConfiguringInterfaceState extends LoggingState {
+ @Override
+ public void enter() {
+ super.enter();
+ mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case EVENT_LINKADDRESS_CONFIGURED:
+ transitionTo(mDhcpBoundState);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class DhcpBoundState extends LoggingState {
+ @Override
+ public void enter() {
+ super.enter();
+ if (mDhcpLease.serverAddress != null && !connectUdpSock(mDhcpLease.serverAddress)) {
+ // There's likely no point in going into DhcpInitState here, we'll probably
+ // just repeat the transaction, get the same IP address as before, and fail.
+ //
+ // NOTE: It is observed that connectUdpSock() basically never fails, due to
+ // SO_BINDTODEVICE. Examining the local socket address shows it will happily
+ // return an IPv4 address from another interface, or even return "0.0.0.0".
+ //
+ // TODO: Consider deleting this check, following testing on several kernels.
+ notifyFailure();
+ transitionTo(mStoppedState);
+ }
+
+ scheduleLeaseTimers();
+ logTimeToBoundState();
+ }
+
+ @Override
+ public void exit() {
+ super.exit();
+ mLastBoundExitTime = SystemClock.elapsedRealtime();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case CMD_RENEW_DHCP:
+ if (mRegisteredForPreDhcpNotification) {
+ transitionTo(mWaitBeforeRenewalState);
+ } else {
+ transitionTo(mDhcpRenewingState);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+
+ private void logTimeToBoundState() {
+ long now = SystemClock.elapsedRealtime();
+ if (mLastBoundExitTime > mLastInitEnterTime) {
+ logState(DhcpClientEvent.RENEWING_BOUND, (int)(now - mLastBoundExitTime));
+ } else {
+ logState(DhcpClientEvent.INITIAL_BOUND, (int)(now - mLastInitEnterTime));
+ }
+ }
+ }
+
+ abstract class DhcpReacquiringState extends PacketRetransmittingState {
+ protected String mLeaseMsg;
+
+ @Override
+ public void enter() {
+ super.enter();
+ startNewTransaction();
+ }
+
+ abstract protected Inet4Address packetDestination();
+
+ protected boolean sendPacket() {
+ return sendRequestPacket(
+ (Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr
+ INADDR_ANY, // DHCP_REQUESTED_IP
+ null, // DHCP_SERVER_IDENTIFIER
+ packetDestination()); // packet destination address
+ }
+
+ protected void receivePacket(DhcpPacket packet) {
+ if (!isValidPacket(packet)) return;
+ if ((packet instanceof DhcpAckPacket)) {
+ final DhcpResults results = packet.toDhcpResults();
+ if (results != null) {
+ if (!mDhcpLease.ipAddress.equals(results.ipAddress)) {
+ Log.d(TAG, "Renewed lease not for our current IP address!");
+ notifyFailure();
+ transitionTo(mDhcpInitState);
+ }
+ setDhcpLeaseExpiry(packet);
+ // Updating our notion of DhcpResults here only causes the
+ // DNS servers and routes to be updated in LinkProperties
+ // in IpManager and by any overridden relevant handlers of
+ // the registered IpManager.Callback. IP address changes
+ // are not supported here.
+ acceptDhcpResults(results, mLeaseMsg);
+ transitionTo(mDhcpBoundState);
+ }
+ } else if (packet instanceof DhcpNakPacket) {
+ Log.d(TAG, "Received NAK, returning to INIT");
+ notifyFailure();
+ transitionTo(mDhcpInitState);
+ }
+ }
+ }
+
+ class DhcpRenewingState extends DhcpReacquiringState {
+ public DhcpRenewingState() {
+ mLeaseMsg = "Renewed";
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (super.processMessage(message) == HANDLED) {
+ return HANDLED;
+ }
+
+ switch (message.what) {
+ case CMD_REBIND_DHCP:
+ transitionTo(mDhcpRebindingState);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+
+ @Override
+ protected Inet4Address packetDestination() {
+ // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but...
+ // http://b/25343517 . Try to make things work anyway by using broadcast renews.
+ return (mDhcpLease.serverAddress != null) ?
+ mDhcpLease.serverAddress : INADDR_BROADCAST;
+ }
+ }
+
+ class DhcpRebindingState extends DhcpReacquiringState {
+ public DhcpRebindingState() {
+ mLeaseMsg = "Rebound";
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+
+ // We need to broadcast and possibly reconnect the socket to a
+ // completely different server.
+ closeQuietly(mUdpSock);
+ if (!initUdpSocket()) {
+ Log.e(TAG, "Failed to recreate UDP socket");
+ transitionTo(mDhcpInitState);
+ }
+ }
+
+ @Override
+ protected Inet4Address packetDestination() {
+ return INADDR_BROADCAST;
+ }
+ }
+
+ class DhcpInitRebootState extends LoggingState {
+ }
+
+ class DhcpRebootingState extends LoggingState {
+ }
+
+ private void logError(int errorCode) {
+ mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode));
+ }
+
+ private void logState(String name, int durationMs) {
+ final DhcpClientEvent event = new DhcpClientEvent.Builder()
+ .setMsg(name)
+ .setDurationMs(durationMs)
+ .build();
+ mMetricsLog.log(mIfaceName, event);
+ }
+}
diff --git a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpDeclinePacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpDiscoverPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpInformPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpInformPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpNakPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpNakPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpOfferPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpOfferPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpReleasePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpReleasePacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpRequestPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpRequestPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
index 14e2936..7b112df 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
@@ -29,7 +29,6 @@
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.SOL_SOCKET;
-import static android.system.OsConstants.SO_BINDTODEVICE;
import static android.system.OsConstants.SO_BROADCAST;
import static android.system.OsConstants.SO_REUSEADDR;
@@ -45,6 +44,7 @@
import android.net.NetworkUtils;
import android.net.TrafficStats;
import android.net.util.SharedLog;
+import android.net.util.SocketUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -629,14 +629,10 @@
final int oldTag = TrafficStats.getAndSetThreadStatsTag(TAG_SYSTEM_DHCP_SERVER);
try {
mSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ SocketUtils.bindSocketToInterface(mSocket, mIfName);
Os.setsockoptInt(mSocket, SOL_SOCKET, SO_REUSEADDR, 1);
- // SO_BINDTODEVICE actually takes a string. This works because the first member
- // of struct ifreq is a NULL-terminated interface name.
- // TODO: add a setsockoptString()
- Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mIfName);
Os.setsockoptInt(mSocket, SOL_SOCKET, SO_BROADCAST, 1);
Os.bind(mSocket, Inet4Address.ANY, DHCP_SERVER);
- NetworkUtils.protectFromVpn(mSocket);
return mSocket;
} catch (IOException | ErrnoException e) {
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
index f38888a..868f3be 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
@@ -30,10 +30,10 @@
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.NetworkUtils;
-
-import com.google.android.collect.Sets;
+import android.util.ArraySet;
import java.net.Inet4Address;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -208,7 +208,7 @@
* but it must always be set explicitly before building the {@link DhcpServingParams}.
*/
public Builder setDefaultRouters(@NonNull Inet4Address... defaultRouters) {
- return setDefaultRouters(Sets.newArraySet(defaultRouters));
+ return setDefaultRouters(new ArraySet<>(Arrays.asList(defaultRouters)));
}
/**
@@ -238,7 +238,7 @@
* building the {@link DhcpServingParams}.
*/
public Builder setDnsServers(@NonNull Inet4Address... dnsServers) {
- return setDnsServers(Sets.newArraySet(dnsServers));
+ return setDnsServers(new ArraySet<>(Arrays.asList(dnsServers)));
}
/**
@@ -268,7 +268,7 @@
* and do not need to be set here.
*/
public Builder setExcludedAddrs(@NonNull Inet4Address... excludedAddrs) {
- return setExcludedAddrs(Sets.newArraySet(excludedAddrs));
+ return setExcludedAddrs(new ArraySet<>(Arrays.asList(excludedAddrs)));
}
/**
diff --git a/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
similarity index 97%
rename from services/net/java/android/net/ip/ConnectivityPacketTracker.java
rename to packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
index bef425a..385dd52 100644
--- a/services/net/java/android/net/ip/ConnectivityPacketTracker.java
+++ b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
@@ -16,6 +16,7 @@
package android.net.ip;
+import static android.net.util.SocketUtils.makePacketSocketAddress;
import static android.system.OsConstants.AF_PACKET;
import static android.system.OsConstants.ARPHRD_ETHER;
import static android.system.OsConstants.ETH_P_ALL;
@@ -28,7 +29,6 @@
import android.os.Handler;
import android.system.ErrnoException;
import android.system.Os;
-import android.system.PacketSocketAddress;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.Log;
@@ -103,7 +103,7 @@
try {
s = Os.socket(AF_PACKET, SOCK_RAW, 0);
NetworkUtils.attachControlPacketFilter(s, ARPHRD_ETHER);
- Os.bind(s, new PacketSocketAddress((short) ETH_P_ALL, mInterface.index));
+ Os.bind(s, makePacketSocketAddress((short) ETH_P_ALL, mInterface.index));
} catch (ErrnoException | IOException e) {
logError("Failed to create packet tracking socket: ", e);
closeFd(s);
diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java
new file mode 100644
index 0000000..ad7f85d
--- /dev/null
+++ b/packages/NetworkStack/src/android/net/ip/IpClient.java
@@ -0,0 +1,1691 @@
+/*
+ * Copyright (C) 2017 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.net.ip;
+
+import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
+import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable;
+
+import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.DhcpResults;
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.ProvisioningConfigurationParcelable;
+import android.net.ProxyInfo;
+import android.net.ProxyInfoParcelable;
+import android.net.RouteInfo;
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter;
+import android.net.dhcp.DhcpClient;
+import android.net.ip.IIpClientCallbacks;
+import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.IpManagerEvent;
+import android.net.shared.InitialConfiguration;
+import android.net.shared.NetdService;
+import android.net.shared.ProvisioningConfiguration;
+import android.net.util.InterfaceParams;
+import android.net.util.SharedLog;
+import android.os.ConditionVariable;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IState;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
+import com.android.server.net.NetlinkTracker;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+
+/**
+ * IpClient
+ *
+ * This class provides the interface to IP-layer provisioning and maintenance
+ * functionality that can be used by transport layers like Wi-Fi, Ethernet,
+ * et cetera.
+ *
+ * [ Lifetime ]
+ * IpClient is designed to be instantiated as soon as the interface name is
+ * known and can be as long-lived as the class containing it (i.e. declaring
+ * it "private final" is okay).
+ *
+ * @hide
+ */
+public class IpClient extends StateMachine {
+ private static final boolean DBG = false;
+
+ // For message logging.
+ private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class };
+ private static final SparseArray<String> sWhatToString =
+ MessageUtils.findMessageNames(sMessageClasses);
+ // Two static concurrent hashmaps of interface name to logging classes.
+ // One holds StateMachine logs and the other connectivity packet logs.
+ private static final ConcurrentHashMap<String, SharedLog> sSmLogs = new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>();
+
+ /**
+ * Dump all state machine and connectivity packet logs to the specified writer.
+ * @param skippedIfaces Interfaces for which logs should not be dumped.
+ */
+ public static void dumpAllLogs(PrintWriter writer, Set<String> skippedIfaces) {
+ for (String ifname : sSmLogs.keySet()) {
+ if (skippedIfaces.contains(ifname)) continue;
+
+ writer.println(String.format("--- BEGIN %s ---", ifname));
+
+ final SharedLog smLog = sSmLogs.get(ifname);
+ if (smLog != null) {
+ writer.println("State machine log:");
+ smLog.dump(null, writer, null);
+ }
+
+ writer.println("");
+
+ final LocalLog pktLog = sPktLogs.get(ifname);
+ if (pktLog != null) {
+ writer.println("Connectivity packet log:");
+ pktLog.readOnlyLocalLog().dump(null, writer, null);
+ }
+
+ writer.println(String.format("--- END %s ---", ifname));
+ }
+ }
+
+ // Use a wrapper class to log in order to ensure complete and detailed
+ // logging. This method is lighter weight than annotations/reflection
+ // and has the following benefits:
+ //
+ // - No invoked method can be forgotten.
+ // Any new method added to IpClient.Callback must be overridden
+ // here or it will never be called.
+ //
+ // - No invoking call site can be forgotten.
+ // Centralized logging in this way means call sites don't need to
+ // remember to log, and therefore no call site can be forgotten.
+ //
+ // - No variation in log format among call sites.
+ // Encourages logging of any available arguments, and all call sites
+ // are necessarily logged identically.
+ //
+ // NOTE: Log first because passed objects may or may not be thread-safe and
+ // once passed on to the callback they may be modified by another thread.
+ //
+ // TODO: Find an lighter weight approach.
+ public static class IpClientCallbacksWrapper {
+ private static final String PREFIX = "INVOKE ";
+ private final IIpClientCallbacks mCallback;
+ private final SharedLog mLog;
+
+ @VisibleForTesting
+ protected IpClientCallbacksWrapper(IIpClientCallbacks callback, SharedLog log) {
+ mCallback = callback;
+ mLog = log;
+ }
+
+ private void log(String msg) {
+ mLog.log(PREFIX + msg);
+ }
+
+ private void log(String msg, Throwable e) {
+ mLog.e(PREFIX + msg, e);
+ }
+
+ public void onPreDhcpAction() {
+ log("onPreDhcpAction()");
+ try {
+ mCallback.onPreDhcpAction();
+ } catch (RemoteException e) {
+ log("Failed to call onPreDhcpAction", e);
+ }
+ }
+
+ public void onPostDhcpAction() {
+ log("onPostDhcpAction()");
+ try {
+ mCallback.onPostDhcpAction();
+ } catch (RemoteException e) {
+ log("Failed to call onPostDhcpAction", e);
+ }
+ }
+
+ public void onNewDhcpResults(DhcpResults dhcpResults) {
+ log("onNewDhcpResults({" + dhcpResults + "})");
+ try {
+ mCallback.onNewDhcpResults(toStableParcelable(dhcpResults));
+ } catch (RemoteException e) {
+ log("Failed to call onNewDhcpResults", e);
+ }
+ }
+
+ public void onProvisioningSuccess(LinkProperties newLp) {
+ log("onProvisioningSuccess({" + newLp + "})");
+ try {
+ mCallback.onProvisioningSuccess(toStableParcelable(newLp));
+ } catch (RemoteException e) {
+ log("Failed to call onProvisioningSuccess", e);
+ }
+ }
+
+ public void onProvisioningFailure(LinkProperties newLp) {
+ log("onProvisioningFailure({" + newLp + "})");
+ try {
+ mCallback.onProvisioningFailure(toStableParcelable(newLp));
+ } catch (RemoteException e) {
+ log("Failed to call onProvisioningFailure", e);
+ }
+ }
+
+ public void onLinkPropertiesChange(LinkProperties newLp) {
+ log("onLinkPropertiesChange({" + newLp + "})");
+ try {
+ mCallback.onLinkPropertiesChange(toStableParcelable(newLp));
+ } catch (RemoteException e) {
+ log("Failed to call onLinkPropertiesChange", e);
+ }
+ }
+
+ public void onReachabilityLost(String logMsg) {
+ log("onReachabilityLost(" + logMsg + ")");
+ try {
+ mCallback.onReachabilityLost(logMsg);
+ } catch (RemoteException e) {
+ log("Failed to call onReachabilityLost", e);
+ }
+ }
+
+ public void onQuit() {
+ log("onQuit()");
+ try {
+ mCallback.onQuit();
+ } catch (RemoteException e) {
+ log("Failed to call onQuit", e);
+ }
+ }
+
+ public void installPacketFilter(byte[] filter) {
+ log("installPacketFilter(byte[" + filter.length + "])");
+ try {
+ mCallback.installPacketFilter(filter);
+ } catch (RemoteException e) {
+ log("Failed to call installPacketFilter", e);
+ }
+ }
+
+ public void startReadPacketFilter() {
+ log("startReadPacketFilter()");
+ try {
+ mCallback.startReadPacketFilter();
+ } catch (RemoteException e) {
+ log("Failed to call startReadPacketFilter", e);
+ }
+ }
+
+ public void setFallbackMulticastFilter(boolean enabled) {
+ log("setFallbackMulticastFilter(" + enabled + ")");
+ try {
+ mCallback.setFallbackMulticastFilter(enabled);
+ } catch (RemoteException e) {
+ log("Failed to call setFallbackMulticastFilter", e);
+ }
+ }
+
+ public void setNeighborDiscoveryOffload(boolean enable) {
+ log("setNeighborDiscoveryOffload(" + enable + ")");
+ try {
+ mCallback.setNeighborDiscoveryOffload(enable);
+ } catch (RemoteException e) {
+ log("Failed to call setNeighborDiscoveryOffload", e);
+ }
+ }
+ }
+
+ public static final String DUMP_ARG_CONFIRM = "confirm";
+
+ private static final int CMD_TERMINATE_AFTER_STOP = 1;
+ private static final int CMD_STOP = 2;
+ private static final int CMD_START = 3;
+ private static final int CMD_CONFIRM = 4;
+ private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5;
+ // Triggered by NetlinkTracker to communicate netlink events.
+ private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
+ private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7;
+ private static final int CMD_UPDATE_HTTP_PROXY = 8;
+ private static final int CMD_SET_MULTICAST_FILTER = 9;
+ private static final int EVENT_PROVISIONING_TIMEOUT = 10;
+ private static final int EVENT_DHCPACTION_TIMEOUT = 11;
+ private static final int EVENT_READ_PACKET_FILTER_COMPLETE = 12;
+
+ // Internal commands to use instead of trying to call transitionTo() inside
+ // a given State's enter() method. Calling transitionTo() from enter/exit
+ // encounters a Log.wtf() that can cause trouble on eng builds.
+ private static final int CMD_JUMP_STARTED_TO_RUNNING = 100;
+ private static final int CMD_JUMP_RUNNING_TO_STOPPING = 101;
+ private static final int CMD_JUMP_STOPPING_TO_STOPPED = 102;
+
+ // IpClient shares a handler with DhcpClient: commands must not overlap
+ public static final int DHCPCLIENT_CMD_BASE = 1000;
+
+ private static final int MAX_LOG_RECORDS = 500;
+ private static final int MAX_PACKET_RECORDS = 100;
+
+ private static final boolean NO_CALLBACKS = false;
+ private static final boolean SEND_CALLBACKS = true;
+
+ // This must match the interface prefix in clatd.c.
+ // TODO: Revert this hack once IpClient and Nat464Xlat work in concert.
+ private static final String CLAT_PREFIX = "v4-";
+
+ private static final int IMMEDIATE_FAILURE_DURATION = 0;
+
+ private static final int PROV_CHANGE_STILL_NOT_PROVISIONED = 1;
+ private static final int PROV_CHANGE_LOST_PROVISIONING = 2;
+ private static final int PROV_CHANGE_GAINED_PROVISIONING = 3;
+ private static final int PROV_CHANGE_STILL_PROVISIONED = 4;
+
+ private final State mStoppedState = new StoppedState();
+ private final State mStoppingState = new StoppingState();
+ private final State mStartedState = new StartedState();
+ private final State mRunningState = new RunningState();
+
+ private final String mTag;
+ private final Context mContext;
+ private final String mInterfaceName;
+ private final String mClatInterfaceName;
+ @VisibleForTesting
+ protected final IpClientCallbacksWrapper mCallback;
+ private final Dependencies mDependencies;
+ private final CountDownLatch mShutdownLatch;
+ private final ConnectivityManager mCm;
+ private final INetworkManagementService mNwService;
+ private final NetlinkTracker mNetlinkTracker;
+ private final WakeupMessage mProvisioningTimeoutAlarm;
+ private final WakeupMessage mDhcpActionTimeoutAlarm;
+ private final SharedLog mLog;
+ private final LocalLog mConnectivityPacketLog;
+ private final MessageHandlingLogger mMsgStateLogger;
+ private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+ private final InterfaceController mInterfaceCtrl;
+
+ private InterfaceParams mInterfaceParams;
+
+ /**
+ * Non-final member variables accessed only from within our StateMachine.
+ */
+ private LinkProperties mLinkProperties;
+ private android.net.shared.ProvisioningConfiguration mConfiguration;
+ private IpReachabilityMonitor mIpReachabilityMonitor;
+ private DhcpClient mDhcpClient;
+ private DhcpResults mDhcpResults;
+ private String mTcpBufferSizes;
+ private ProxyInfo mHttpProxy;
+ private ApfFilter mApfFilter;
+ private boolean mMulticastFiltering;
+ private long mStartTimeMillis;
+
+ /**
+ * Reading the snapshot is an asynchronous operation initiated by invoking
+ * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an
+ * EVENT_READ_PACKET_FILTER_COMPLETE message. The mApfDataSnapshotComplete condition variable
+ * signals when a new snapshot is ready.
+ */
+ private final ConditionVariable mApfDataSnapshotComplete = new ConditionVariable();
+
+ public static class Dependencies {
+ public INetworkManagementService getNMS() {
+ return INetworkManagementService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+ }
+
+ public INetd getNetd() {
+ return NetdService.getInstance();
+ }
+
+ /**
+ * Get interface parameters for the specified interface.
+ */
+ public InterfaceParams getInterfaceParams(String ifname) {
+ return InterfaceParams.getByName(ifname);
+ }
+ }
+
+ public IpClient(Context context, String ifName, IIpClientCallbacks callback) {
+ this(context, ifName, callback, new Dependencies());
+ }
+
+ /**
+ * An expanded constructor, useful for dependency injection.
+ * TODO: migrate all test users to mock IpClient directly and remove this ctor.
+ */
+ public IpClient(Context context, String ifName, IIpClientCallbacks callback,
+ INetworkManagementService nwService) {
+ this(context, ifName, callback, new Dependencies() {
+ @Override
+ public INetworkManagementService getNMS() {
+ return nwService;
+ }
+ });
+ }
+
+ @VisibleForTesting
+ IpClient(Context context, String ifName, IIpClientCallbacks callback, Dependencies deps) {
+ super(IpClient.class.getSimpleName() + "." + ifName);
+ Preconditions.checkNotNull(ifName);
+ Preconditions.checkNotNull(callback);
+
+ mTag = getName();
+
+ mContext = context;
+ mInterfaceName = ifName;
+ mClatInterfaceName = CLAT_PREFIX + ifName;
+ mDependencies = deps;
+ mShutdownLatch = new CountDownLatch(1);
+ mCm = mContext.getSystemService(ConnectivityManager.class);
+ mNwService = deps.getNMS();
+
+ sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag));
+ mLog = sSmLogs.get(mInterfaceName);
+ sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS));
+ mConnectivityPacketLog = sPktLogs.get(mInterfaceName);
+ mMsgStateLogger = new MessageHandlingLogger();
+ mCallback = new IpClientCallbacksWrapper(callback, mLog);
+
+ // TODO: Consider creating, constructing, and passing in some kind of
+ // InterfaceController.Dependencies class.
+ mInterfaceCtrl = new InterfaceController(mInterfaceName, deps.getNetd(), mLog);
+
+ mNetlinkTracker = new NetlinkTracker(
+ mInterfaceName,
+ new NetlinkTracker.Callback() {
+ @Override
+ public void update() {
+ sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+ }
+ }) {
+ @Override
+ public void interfaceAdded(String iface) {
+ super.interfaceAdded(iface);
+ if (mClatInterfaceName.equals(iface)) {
+ mCallback.setNeighborDiscoveryOffload(false);
+ } else if (!mInterfaceName.equals(iface)) {
+ return;
+ }
+
+ final String msg = "interfaceAdded(" + iface + ")";
+ logMsg(msg);
+ }
+
+ @Override
+ public void interfaceRemoved(String iface) {
+ super.interfaceRemoved(iface);
+ // TODO: Also observe mInterfaceName going down and take some
+ // kind of appropriate action.
+ if (mClatInterfaceName.equals(iface)) {
+ // TODO: consider sending a message to the IpClient main
+ // StateMachine thread, in case "NDO enabled" state becomes
+ // tied to more things that 464xlat operation.
+ mCallback.setNeighborDiscoveryOffload(true);
+ } else if (!mInterfaceName.equals(iface)) {
+ return;
+ }
+
+ final String msg = "interfaceRemoved(" + iface + ")";
+ logMsg(msg);
+ }
+
+ private void logMsg(String msg) {
+ Log.d(mTag, msg);
+ getHandler().post(() -> mLog.log("OBSERVED " + msg));
+ }
+ };
+
+ mLinkProperties = new LinkProperties();
+ mLinkProperties.setInterfaceName(mInterfaceName);
+
+ mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+ mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
+ mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+ mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
+
+ // Anything the StateMachine may access must have been instantiated
+ // before this point.
+ configureAndStartStateMachine();
+
+ // Anything that may send messages to the StateMachine must only be
+ // configured to do so after the StateMachine has started (above).
+ startStateMachineUpdaters();
+ }
+
+ /**
+ * Make a IIpClient connector to communicate with this IpClient.
+ */
+ public IIpClient makeConnector() {
+ return new IpClientConnector();
+ }
+
+ class IpClientConnector extends IIpClient.Stub {
+ @Override
+ public void completedPreDhcpAction() {
+ checkNetworkStackCallingPermission();
+ IpClient.this.completedPreDhcpAction();
+ }
+ @Override
+ public void confirmConfiguration() {
+ checkNetworkStackCallingPermission();
+ IpClient.this.confirmConfiguration();
+ }
+ @Override
+ public void readPacketFilterComplete(byte[] data) {
+ checkNetworkStackCallingPermission();
+ IpClient.this.readPacketFilterComplete(data);
+ }
+ @Override
+ public void shutdown() {
+ checkNetworkStackCallingPermission();
+ IpClient.this.shutdown();
+ }
+ @Override
+ public void startProvisioning(ProvisioningConfigurationParcelable req) {
+ checkNetworkStackCallingPermission();
+ IpClient.this.startProvisioning(ProvisioningConfiguration.fromStableParcelable(req));
+ }
+ @Override
+ public void stop() {
+ checkNetworkStackCallingPermission();
+ IpClient.this.stop();
+ }
+ @Override
+ public void setTcpBufferSizes(String tcpBufferSizes) {
+ checkNetworkStackCallingPermission();
+ IpClient.this.setTcpBufferSizes(tcpBufferSizes);
+ }
+ @Override
+ public void setHttpProxy(ProxyInfoParcelable proxyInfo) {
+ checkNetworkStackCallingPermission();
+ IpClient.this.setHttpProxy(fromStableParcelable(proxyInfo));
+ }
+ @Override
+ public void setMulticastFilter(boolean enabled) {
+ checkNetworkStackCallingPermission();
+ IpClient.this.setMulticastFilter(enabled);
+ }
+ }
+
+ public String getInterfaceName() {
+ return mInterfaceName;
+ }
+
+ private void configureAndStartStateMachine() {
+ // CHECKSTYLE:OFF IndentationCheck
+ addState(mStoppedState);
+ addState(mStartedState);
+ addState(mRunningState, mStartedState);
+ addState(mStoppingState);
+ // CHECKSTYLE:ON IndentationCheck
+
+ setInitialState(mStoppedState);
+
+ super.start();
+ }
+
+ private void startStateMachineUpdaters() {
+ try {
+ mNwService.registerObserver(mNetlinkTracker);
+ } catch (RemoteException e) {
+ logError("Couldn't register NetlinkTracker: %s", e);
+ }
+ }
+
+ private void stopStateMachineUpdaters() {
+ try {
+ mNwService.unregisterObserver(mNetlinkTracker);
+ } catch (RemoteException e) {
+ logError("Couldn't unregister NetlinkTracker: %s", e);
+ }
+ }
+
+ @Override
+ protected void onQuitting() {
+ mCallback.onQuit();
+ mShutdownLatch.countDown();
+ }
+
+ /**
+ * Shut down this IpClient instance altogether.
+ */
+ public void shutdown() {
+ stop();
+ sendMessage(CMD_TERMINATE_AFTER_STOP);
+ }
+
+ /**
+ * Start provisioning with the provided parameters.
+ */
+ public void startProvisioning(ProvisioningConfiguration req) {
+ if (!req.isValid()) {
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+ return;
+ }
+
+ mInterfaceParams = mDependencies.getInterfaceParams(mInterfaceName);
+ if (mInterfaceParams == null) {
+ logError("Failed to find InterfaceParams for " + mInterfaceName);
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND);
+ return;
+ }
+
+ mCallback.setNeighborDiscoveryOffload(true);
+ sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req));
+ }
+
+ /**
+ * Stop this IpClient.
+ *
+ * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}.
+ */
+ public void stop() {
+ sendMessage(CMD_STOP);
+ }
+
+ /**
+ * Confirm the provisioning configuration.
+ */
+ public void confirmConfiguration() {
+ sendMessage(CMD_CONFIRM);
+ }
+
+ /**
+ * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be
+ * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to
+ * proceed.
+ */
+ public void completedPreDhcpAction() {
+ sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+ }
+
+ /**
+ * Indicate that packet filter read is complete.
+ */
+ public void readPacketFilterComplete(byte[] data) {
+ sendMessage(EVENT_READ_PACKET_FILTER_COMPLETE, data);
+ }
+
+ /**
+ * Set the TCP buffer sizes to use.
+ *
+ * This may be called, repeatedly, at any time before or after a call to
+ * #startProvisioning(). The setting is cleared upon calling #stop().
+ */
+ public void setTcpBufferSizes(String tcpBufferSizes) {
+ sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
+ }
+
+ /**
+ * Set the HTTP Proxy configuration to use.
+ *
+ * This may be called, repeatedly, at any time before or after a call to
+ * #startProvisioning(). The setting is cleared upon calling #stop().
+ */
+ public void setHttpProxy(ProxyInfo proxyInfo) {
+ sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
+ }
+
+ /**
+ * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering,
+ * if not, Callback.setFallbackMulticastFilter() is called.
+ */
+ public void setMulticastFilter(boolean enabled) {
+ sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
+ }
+
+ /**
+ * Dump logs of this IpClient.
+ */
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
+ // Execute confirmConfiguration() and take no further action.
+ confirmConfiguration();
+ return;
+ }
+
+ // Thread-unsafe access to mApfFilter but just used for debugging.
+ final ApfFilter apfFilter = mApfFilter;
+ final android.net.shared.ProvisioningConfiguration provisioningConfig = mConfiguration;
+ final ApfCapabilities apfCapabilities = (provisioningConfig != null)
+ ? provisioningConfig.mApfCapabilities : null;
+
+ IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+ pw.println(mTag + " APF dump:");
+ pw.increaseIndent();
+ if (apfFilter != null) {
+ if (apfCapabilities.hasDataAccess()) {
+ // Request a new snapshot, then wait for it.
+ mApfDataSnapshotComplete.close();
+ mCallback.startReadPacketFilter();
+ if (!mApfDataSnapshotComplete.block(1000)) {
+ pw.print("TIMEOUT: DUMPING STALE APF SNAPSHOT");
+ }
+ }
+ apfFilter.dump(pw);
+
+ } else {
+ pw.print("No active ApfFilter; ");
+ if (provisioningConfig == null) {
+ pw.println("IpClient not yet started.");
+ } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
+ pw.println("Hardware does not support APF.");
+ } else {
+ pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
+ }
+ }
+ pw.decreaseIndent();
+ pw.println();
+ pw.println(mTag + " current ProvisioningConfiguration:");
+ pw.increaseIndent();
+ pw.println(Objects.toString(provisioningConfig, "N/A"));
+ pw.decreaseIndent();
+
+ final IpReachabilityMonitor iprm = mIpReachabilityMonitor;
+ if (iprm != null) {
+ pw.println();
+ pw.println(mTag + " current IpReachabilityMonitor state:");
+ pw.increaseIndent();
+ iprm.dump(pw);
+ pw.decreaseIndent();
+ }
+
+ pw.println();
+ pw.println(mTag + " StateMachine dump:");
+ pw.increaseIndent();
+ mLog.dump(fd, pw, args);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println(mTag + " connectivity packet log:");
+ pw.println();
+ pw.println("Debug with python and scapy via:");
+ pw.println("shell$ python");
+ pw.println(">>> from scapy import all as scapy");
+ pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
+ pw.println();
+
+ pw.increaseIndent();
+ mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
+ pw.decreaseIndent();
+ }
+
+
+ /**
+ * Internals.
+ */
+
+ @Override
+ protected String getWhatToString(int what) {
+ return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
+ }
+
+ @Override
+ protected String getLogRecString(Message msg) {
+ final String logLine = String.format(
+ "%s/%d %d %d %s [%s]",
+ mInterfaceName, (mInterfaceParams == null) ? -1 : mInterfaceParams.index,
+ msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
+
+ final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
+ mLog.log(richerLogLine);
+ if (DBG) {
+ Log.d(mTag, richerLogLine);
+ }
+
+ mMsgStateLogger.reset();
+ return logLine;
+ }
+
+ @Override
+ protected boolean recordLogRec(Message msg) {
+ // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
+ // and we already log any LinkProperties change that results in an
+ // invocation of IpClient.Callback#onLinkPropertiesChange().
+ final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+ if (!shouldLog) {
+ mMsgStateLogger.reset();
+ }
+ return shouldLog;
+ }
+
+ private void logError(String fmt, Object... args) {
+ final String msg = "ERROR " + String.format(fmt, args);
+ Log.e(mTag, msg);
+ mLog.log(msg);
+ }
+
+ // This needs to be called with care to ensure that our LinkProperties
+ // are in sync with the actual LinkProperties of the interface. For example,
+ // we should only call this if we know for sure that there are no IP addresses
+ // assigned to the interface, etc.
+ private void resetLinkProperties() {
+ mNetlinkTracker.clearLinkProperties();
+ mConfiguration = null;
+ mDhcpResults = null;
+ mTcpBufferSizes = "";
+ mHttpProxy = null;
+
+ mLinkProperties = new LinkProperties();
+ mLinkProperties.setInterfaceName(mInterfaceName);
+ }
+
+ private void recordMetric(final int type) {
+ // We may record error metrics prior to starting.
+ // Map this to IMMEDIATE_FAILURE_DURATION.
+ final long duration = (mStartTimeMillis > 0)
+ ? (SystemClock.elapsedRealtime() - mStartTimeMillis)
+ : IMMEDIATE_FAILURE_DURATION;
+ mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
+ }
+
+ // For now: use WifiStateMachine's historical notion of provisioned.
+ @VisibleForTesting
+ static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
+ // For historical reasons, we should connect even if all we have is
+ // an IPv4 address and nothing else.
+ if (lp.hasIPv4Address() || lp.isProvisioned()) {
+ return true;
+ }
+ if (config == null) {
+ return false;
+ }
+
+ // When an InitialConfiguration is specified, ignore any difference with previous
+ // properties and instead check if properties observed match the desired properties.
+ return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
+ }
+
+ // TODO: Investigate folding all this into the existing static function
+ // LinkProperties.compareProvisioning() or some other single function that
+ // takes two LinkProperties objects and returns a ProvisioningChange
+ // object that is a correct and complete assessment of what changed, taking
+ // account of the asymmetries described in the comments in this function.
+ // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
+ private int compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
+ int delta;
+ InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
+ final boolean wasProvisioned = isProvisioned(oldLp, config);
+ final boolean isProvisioned = isProvisioned(newLp, config);
+
+ if (!wasProvisioned && isProvisioned) {
+ delta = PROV_CHANGE_GAINED_PROVISIONING;
+ } else if (wasProvisioned && isProvisioned) {
+ delta = PROV_CHANGE_STILL_PROVISIONED;
+ } else if (!wasProvisioned && !isProvisioned) {
+ delta = PROV_CHANGE_STILL_NOT_PROVISIONED;
+ } else {
+ // (wasProvisioned && !isProvisioned)
+ //
+ // Note that this is true even if we lose a configuration element
+ // (e.g., a default gateway) that would not be required to advance
+ // into provisioned state. This is intended: if we have a default
+ // router and we lose it, that's a sure sign of a problem, but if
+ // we connect to a network with no IPv4 DNS servers, we consider
+ // that to be a network without DNS servers and connect anyway.
+ //
+ // See the comment below.
+ delta = PROV_CHANGE_LOST_PROVISIONING;
+ }
+
+ final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
+ final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
+ final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
+
+ // If bad wifi avoidance is disabled, then ignore IPv6 loss of
+ // provisioning. Otherwise, when a hotspot that loses Internet
+ // access sends out a 0-lifetime RA to its clients, the clients
+ // will disconnect and then reconnect, avoiding the bad hotspot,
+ // instead of getting stuck on the bad hotspot. http://b/31827713 .
+ //
+ // This is incorrect because if the hotspot then regains Internet
+ // access with a different prefix, TCP connections on the
+ // deprecated addresses will remain stuck.
+ //
+ // Note that we can still be disconnected by IpReachabilityMonitor
+ // if the IPv6 default gateway (but not the IPv6 DNS servers; see
+ // accompanying code in IpReachabilityMonitor) is unreachable.
+ final boolean ignoreIPv6ProvisioningLoss =
+ mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
+ && mCm.getAvoidBadWifi();
+
+ // Additionally:
+ //
+ // Partial configurations (e.g., only an IPv4 address with no DNS
+ // servers and no default route) are accepted as long as DHCPv4
+ // succeeds. On such a network, isProvisioned() will always return
+ // false, because the configuration is not complete, but we want to
+ // connect anyway. It might be a disconnected network such as a
+ // Chromecast or a wireless printer, for example.
+ //
+ // Because on such a network isProvisioned() will always return false,
+ // delta will never be LOST_PROVISIONING. So check for loss of
+ // provisioning here too.
+ if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
+ delta = PROV_CHANGE_LOST_PROVISIONING;
+ }
+
+ // Additionally:
+ //
+ // If the previous link properties had a global IPv6 address and an
+ // IPv6 default route then also consider the loss of that default route
+ // to be a loss of provisioning. See b/27962810.
+ if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
+ delta = PROV_CHANGE_LOST_PROVISIONING;
+ }
+
+ return delta;
+ }
+
+ private void dispatchCallback(int delta, LinkProperties newLp) {
+ switch (delta) {
+ case PROV_CHANGE_GAINED_PROVISIONING:
+ if (DBG) {
+ Log.d(mTag, "onProvisioningSuccess()");
+ }
+ recordMetric(IpManagerEvent.PROVISIONING_OK);
+ mCallback.onProvisioningSuccess(newLp);
+ break;
+
+ case PROV_CHANGE_LOST_PROVISIONING:
+ if (DBG) {
+ Log.d(mTag, "onProvisioningFailure()");
+ }
+ recordMetric(IpManagerEvent.PROVISIONING_FAIL);
+ mCallback.onProvisioningFailure(newLp);
+ break;
+
+ default:
+ if (DBG) {
+ Log.d(mTag, "onLinkPropertiesChange()");
+ }
+ mCallback.onLinkPropertiesChange(newLp);
+ break;
+ }
+ }
+
+ // Updates all IpClient-related state concerned with LinkProperties.
+ // Returns a ProvisioningChange for possibly notifying other interested
+ // parties that are not fronted by IpClient.
+ private int setLinkProperties(LinkProperties newLp) {
+ if (mApfFilter != null) {
+ mApfFilter.setLinkProperties(newLp);
+ }
+ if (mIpReachabilityMonitor != null) {
+ mIpReachabilityMonitor.updateLinkProperties(newLp);
+ }
+
+ int delta = compareProvisioning(mLinkProperties, newLp);
+ mLinkProperties = new LinkProperties(newLp);
+
+ if (delta == PROV_CHANGE_GAINED_PROVISIONING) {
+ // TODO: Add a proper ProvisionedState and cancel the alarm in
+ // its enter() method.
+ mProvisioningTimeoutAlarm.cancel();
+ }
+
+ return delta;
+ }
+
+ private LinkProperties assembleLinkProperties() {
+ // [1] Create a new LinkProperties object to populate.
+ LinkProperties newLp = new LinkProperties();
+ newLp.setInterfaceName(mInterfaceName);
+
+ // [2] Pull in data from netlink:
+ // - IPv4 addresses
+ // - IPv6 addresses
+ // - IPv6 routes
+ // - IPv6 DNS servers
+ //
+ // N.B.: this is fundamentally race-prone and should be fixed by
+ // changing NetlinkTracker from a hybrid edge/level model to an
+ // edge-only model, or by giving IpClient its own netlink socket(s)
+ // so as to track all required information directly.
+ LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
+ newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
+ for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
+ newLp.addRoute(route);
+ }
+ addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
+
+ // [3] Add in data from DHCPv4, if available.
+ //
+ // mDhcpResults is never shared with any other owner so we don't have
+ // to worry about concurrent modification.
+ if (mDhcpResults != null) {
+ for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
+ newLp.addRoute(route);
+ }
+ addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
+ newLp.setDomains(mDhcpResults.domains);
+
+ if (mDhcpResults.mtu != 0) {
+ newLp.setMtu(mDhcpResults.mtu);
+ }
+ }
+
+ // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
+ if (!TextUtils.isEmpty(mTcpBufferSizes)) {
+ newLp.setTcpBufferSizes(mTcpBufferSizes);
+ }
+ if (mHttpProxy != null) {
+ newLp.setHttpProxy(mHttpProxy);
+ }
+
+ // [5] Add data from InitialConfiguration
+ if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
+ InitialConfiguration config = mConfiguration.mInitialConfig;
+ // Add InitialConfiguration routes and dns server addresses once all addresses
+ // specified in the InitialConfiguration have been observed with Netlink.
+ if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
+ for (IpPrefix prefix : config.directlyConnectedRoutes) {
+ newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
+ }
+ }
+ addAllReachableDnsServers(newLp, config.dnsServers);
+ }
+ final LinkProperties oldLp = mLinkProperties;
+ if (DBG) {
+ Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
+ netlinkLinkProperties, newLp, oldLp));
+ }
+
+ // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
+ // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
+ return newLp;
+ }
+
+ private static void addAllReachableDnsServers(
+ LinkProperties lp, Iterable<InetAddress> dnses) {
+ // TODO: Investigate deleting this reachability check. We should be
+ // able to pass everything down to netd and let netd do evaluation
+ // and RFC6724-style sorting.
+ for (InetAddress dns : dnses) {
+ if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
+ lp.addDnsServer(dns);
+ }
+ }
+ }
+
+ // Returns false if we have lost provisioning, true otherwise.
+ private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
+ final LinkProperties newLp = assembleLinkProperties();
+ if (Objects.equals(newLp, mLinkProperties)) {
+ return true;
+ }
+ final int delta = setLinkProperties(newLp);
+ if (sendCallbacks) {
+ dispatchCallback(delta, newLp);
+ }
+ return (delta != PROV_CHANGE_LOST_PROVISIONING);
+ }
+
+ private void handleIPv4Success(DhcpResults dhcpResults) {
+ mDhcpResults = new DhcpResults(dhcpResults);
+ final LinkProperties newLp = assembleLinkProperties();
+ final int delta = setLinkProperties(newLp);
+
+ if (DBG) {
+ Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
+ }
+ mCallback.onNewDhcpResults(dhcpResults);
+ dispatchCallback(delta, newLp);
+ }
+
+ private void handleIPv4Failure() {
+ // TODO: Investigate deleting this clearIPv4Address() call.
+ //
+ // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
+ // that could trigger a call to this function. If we missed handling
+ // that message in StartedState for some reason we would still clear
+ // any addresses upon entry to StoppedState.
+ mInterfaceCtrl.clearIPv4Address();
+ mDhcpResults = null;
+ if (DBG) {
+ Log.d(mTag, "onNewDhcpResults(null)");
+ }
+ mCallback.onNewDhcpResults(null);
+
+ handleProvisioningFailure();
+ }
+
+ private void handleProvisioningFailure() {
+ final LinkProperties newLp = assembleLinkProperties();
+ int delta = setLinkProperties(newLp);
+ // If we've gotten here and we're still not provisioned treat that as
+ // a total loss of provisioning.
+ //
+ // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
+ // there was no usable IPv6 obtained before a non-zero provisioning
+ // timeout expired.
+ //
+ // Regardless: GAME OVER.
+ if (delta == PROV_CHANGE_STILL_NOT_PROVISIONED) {
+ delta = PROV_CHANGE_LOST_PROVISIONING;
+ }
+
+ dispatchCallback(delta, newLp);
+ if (delta == PROV_CHANGE_LOST_PROVISIONING) {
+ transitionTo(mStoppingState);
+ }
+ }
+
+ private void doImmediateProvisioningFailure(int failureType) {
+ logError("onProvisioningFailure(): %s", failureType);
+ recordMetric(failureType);
+ mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
+ }
+
+ private boolean startIPv4() {
+ // If we have a StaticIpConfiguration attempt to apply it and
+ // handle the result accordingly.
+ if (mConfiguration.mStaticIpConfig != null) {
+ if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
+ handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
+ } else {
+ return false;
+ }
+ } else {
+ // Start DHCPv4.
+ mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceParams);
+ mDhcpClient.registerForPreDhcpNotification();
+ mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
+ }
+
+ return true;
+ }
+
+ private boolean startIPv6() {
+ return mInterfaceCtrl.setIPv6PrivacyExtensions(true)
+ && mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode)
+ && mInterfaceCtrl.enableIPv6();
+ }
+
+ private boolean applyInitialConfig(InitialConfiguration config) {
+ // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
+ for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
+ if (!mInterfaceCtrl.addAddress(addr)) return false;
+ }
+
+ return true;
+ }
+
+ private boolean startIpReachabilityMonitor() {
+ try {
+ // TODO: Fetch these parameters from settings, and install a
+ // settings observer to watch for update and re-program these
+ // parameters (Q: is this level of dynamic updatability really
+ // necessary or does reading from settings at startup suffice?).
+ final int numSolicits = 5;
+ final int interSolicitIntervalMs = 750;
+ setNeighborParameters(mDependencies.getNetd(), mInterfaceName,
+ numSolicits, interSolicitIntervalMs);
+ } catch (Exception e) {
+ mLog.e("Failed to adjust neighbor parameters", e);
+ // Carry on using the system defaults (currently: 3, 1000);
+ }
+
+ try {
+ mIpReachabilityMonitor = new IpReachabilityMonitor(
+ mContext,
+ mInterfaceParams,
+ getHandler(),
+ mLog,
+ new IpReachabilityMonitor.Callback() {
+ @Override
+ public void notifyLost(InetAddress ip, String logMsg) {
+ mCallback.onReachabilityLost(logMsg);
+ }
+ },
+ mConfiguration.mUsingMultinetworkPolicyTracker);
+ } catch (IllegalArgumentException iae) {
+ // Failed to start IpReachabilityMonitor. Log it and call
+ // onProvisioningFailure() immediately.
+ //
+ // See http://b/31038971.
+ logError("IpReachabilityMonitor failure: %s", iae);
+ mIpReachabilityMonitor = null;
+ }
+
+ return (mIpReachabilityMonitor != null);
+ }
+
+ private void stopAllIP() {
+ // We don't need to worry about routes, just addresses, because:
+ // - disableIpv6() will clear autoconf IPv6 routes as well, and
+ // - we don't get IPv4 routes from netlink
+ // so we neither react to nor need to wait for changes in either.
+
+ mInterfaceCtrl.disableIPv6();
+ mInterfaceCtrl.clearAllAddresses();
+ }
+
+ class StoppedState extends State {
+ @Override
+ public void enter() {
+ stopAllIP();
+
+ resetLinkProperties();
+ if (mStartTimeMillis > 0) {
+ // Completed a life-cycle; send a final empty LinkProperties
+ // (cleared in resetLinkProperties() above) and record an event.
+ mCallback.onLinkPropertiesChange(new LinkProperties(mLinkProperties));
+ recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
+ mStartTimeMillis = 0;
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_TERMINATE_AFTER_STOP:
+ stopStateMachineUpdaters();
+ quit();
+ break;
+
+ case CMD_STOP:
+ break;
+
+ case CMD_START:
+ mConfiguration = (android.net.shared.ProvisioningConfiguration) msg.obj;
+ transitionTo(mStartedState);
+ break;
+
+ case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ break;
+
+ case CMD_UPDATE_TCP_BUFFER_SIZES:
+ mTcpBufferSizes = (String) msg.obj;
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ break;
+
+ case CMD_UPDATE_HTTP_PROXY:
+ mHttpProxy = (ProxyInfo) msg.obj;
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ break;
+
+ case CMD_SET_MULTICAST_FILTER:
+ mMulticastFiltering = (boolean) msg.obj;
+ break;
+
+ case DhcpClient.CMD_ON_QUIT:
+ // Everything is already stopped.
+ logError("Unexpected CMD_ON_QUIT (already stopped).");
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+ }
+
+ class StoppingState extends State {
+ @Override
+ public void enter() {
+ if (mDhcpClient == null) {
+ // There's no DHCPv4 for which to wait; proceed to stopped.
+ deferMessage(obtainMessage(CMD_JUMP_STOPPING_TO_STOPPED));
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_JUMP_STOPPING_TO_STOPPED:
+ transitionTo(mStoppedState);
+ break;
+
+ case CMD_STOP:
+ break;
+
+ case DhcpClient.CMD_CLEAR_LINKADDRESS:
+ mInterfaceCtrl.clearIPv4Address();
+ break;
+
+ case DhcpClient.CMD_ON_QUIT:
+ mDhcpClient = null;
+ transitionTo(mStoppedState);
+ break;
+
+ default:
+ deferMessage(msg);
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+ }
+
+ class StartedState extends State {
+ @Override
+ public void enter() {
+ mStartTimeMillis = SystemClock.elapsedRealtime();
+
+ if (mConfiguration.mProvisioningTimeoutMs > 0) {
+ final long alarmTime = SystemClock.elapsedRealtime()
+ + mConfiguration.mProvisioningTimeoutMs;
+ mProvisioningTimeoutAlarm.schedule(alarmTime);
+ }
+
+ if (readyToProceed()) {
+ deferMessage(obtainMessage(CMD_JUMP_STARTED_TO_RUNNING));
+ } else {
+ // Clear all IPv4 and IPv6 before proceeding to RunningState.
+ // Clean up any leftover state from an abnormal exit from
+ // tethering or during an IpClient restart.
+ stopAllIP();
+ }
+ }
+
+ @Override
+ public void exit() {
+ mProvisioningTimeoutAlarm.cancel();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_JUMP_STARTED_TO_RUNNING:
+ transitionTo(mRunningState);
+ break;
+
+ case CMD_STOP:
+ transitionTo(mStoppingState);
+ break;
+
+ case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ if (readyToProceed()) {
+ transitionTo(mRunningState);
+ }
+ break;
+
+ case EVENT_PROVISIONING_TIMEOUT:
+ handleProvisioningFailure();
+ break;
+
+ default:
+ // It's safe to process messages out of order because the
+ // only message that can both
+ // a) be received at this time and
+ // b) affect provisioning state
+ // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
+ deferMessage(msg);
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+
+ private boolean readyToProceed() {
+ return (!mLinkProperties.hasIPv4Address() && !mLinkProperties.hasGlobalIPv6Address());
+ }
+ }
+
+ class RunningState extends State {
+ private ConnectivityPacketTracker mPacketTracker;
+ private boolean mDhcpActionInFlight;
+
+ @Override
+ public void enter() {
+ ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration();
+ apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;
+ apfConfig.multicastFilter = mMulticastFiltering;
+ // Get the Configuration for ApfFilter from Context
+ apfConfig.ieee802_3Filter =
+ mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
+ apfConfig.ethTypeBlackList =
+ mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList);
+ mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback);
+ // TODO: investigate the effects of any multicast filtering racing/interfering with the
+ // rest of this IP configuration startup.
+ if (mApfFilter == null) {
+ mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+ }
+
+ mPacketTracker = createPacketTracker();
+ if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
+
+ if (mConfiguration.mEnableIPv6 && !startIPv6()) {
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
+ enqueueJumpToStoppingState();
+ return;
+ }
+
+ if (mConfiguration.mEnableIPv4 && !startIPv4()) {
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
+ enqueueJumpToStoppingState();
+ return;
+ }
+
+ final InitialConfiguration config = mConfiguration.mInitialConfig;
+ if ((config != null) && !applyInitialConfig(config)) {
+ // TODO introduce a new IpManagerEvent constant to distinguish this error case.
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+ enqueueJumpToStoppingState();
+ return;
+ }
+
+ if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
+ doImmediateProvisioningFailure(
+ IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
+ enqueueJumpToStoppingState();
+ return;
+ }
+ }
+
+ @Override
+ public void exit() {
+ stopDhcpAction();
+
+ if (mIpReachabilityMonitor != null) {
+ mIpReachabilityMonitor.stop();
+ mIpReachabilityMonitor = null;
+ }
+
+ if (mDhcpClient != null) {
+ mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
+ mDhcpClient.doQuit();
+ }
+
+ if (mPacketTracker != null) {
+ mPacketTracker.stop();
+ mPacketTracker = null;
+ }
+
+ if (mApfFilter != null) {
+ mApfFilter.shutdown();
+ mApfFilter = null;
+ }
+
+ resetLinkProperties();
+ }
+
+ private void enqueueJumpToStoppingState() {
+ deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING));
+ }
+
+ private ConnectivityPacketTracker createPacketTracker() {
+ try {
+ return new ConnectivityPacketTracker(
+ getHandler(), mInterfaceParams, mConnectivityPacketLog);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ private void ensureDhcpAction() {
+ if (!mDhcpActionInFlight) {
+ mCallback.onPreDhcpAction();
+ mDhcpActionInFlight = true;
+ final long alarmTime = SystemClock.elapsedRealtime()
+ + mConfiguration.mRequestedPreDhcpActionMs;
+ mDhcpActionTimeoutAlarm.schedule(alarmTime);
+ }
+ }
+
+ private void stopDhcpAction() {
+ mDhcpActionTimeoutAlarm.cancel();
+ if (mDhcpActionInFlight) {
+ mCallback.onPostDhcpAction();
+ mDhcpActionInFlight = false;
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_JUMP_RUNNING_TO_STOPPING:
+ case CMD_STOP:
+ transitionTo(mStoppingState);
+ break;
+
+ case CMD_START:
+ logError("ALERT: START received in StartedState. Please fix caller.");
+ break;
+
+ case CMD_CONFIRM:
+ // TODO: Possibly introduce a second type of confirmation
+ // that both probes (a) on-link neighbors and (b) does
+ // a DHCPv4 RENEW. We used to do this on Wi-Fi framework
+ // roams.
+ if (mIpReachabilityMonitor != null) {
+ mIpReachabilityMonitor.probeAll();
+ }
+ break;
+
+ case EVENT_PRE_DHCP_ACTION_COMPLETE:
+ // It's possible to reach here if, for example, someone
+ // calls completedPreDhcpAction() after provisioning with
+ // a static IP configuration.
+ if (mDhcpClient != null) {
+ mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
+ }
+ break;
+
+ case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+ if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
+ transitionTo(mStoppingState);
+ }
+ break;
+
+ case CMD_UPDATE_TCP_BUFFER_SIZES:
+ mTcpBufferSizes = (String) msg.obj;
+ // This cannot possibly change provisioning state.
+ handleLinkPropertiesUpdate(SEND_CALLBACKS);
+ break;
+
+ case CMD_UPDATE_HTTP_PROXY:
+ mHttpProxy = (ProxyInfo) msg.obj;
+ // This cannot possibly change provisioning state.
+ handleLinkPropertiesUpdate(SEND_CALLBACKS);
+ break;
+
+ case CMD_SET_MULTICAST_FILTER: {
+ mMulticastFiltering = (boolean) msg.obj;
+ if (mApfFilter != null) {
+ mApfFilter.setMulticastFilter(mMulticastFiltering);
+ } else {
+ mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+ }
+ break;
+ }
+
+ case EVENT_READ_PACKET_FILTER_COMPLETE: {
+ if (mApfFilter != null) {
+ mApfFilter.setDataSnapshot((byte[]) msg.obj);
+ }
+ mApfDataSnapshotComplete.open();
+ break;
+ }
+
+ case EVENT_DHCPACTION_TIMEOUT:
+ stopDhcpAction();
+ break;
+
+ case DhcpClient.CMD_PRE_DHCP_ACTION:
+ if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
+ ensureDhcpAction();
+ } else {
+ sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+ }
+ break;
+
+ case DhcpClient.CMD_CLEAR_LINKADDRESS:
+ mInterfaceCtrl.clearIPv4Address();
+ break;
+
+ case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
+ final LinkAddress ipAddress = (LinkAddress) msg.obj;
+ if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
+ mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
+ } else {
+ logError("Failed to set IPv4 address.");
+ dispatchCallback(PROV_CHANGE_LOST_PROVISIONING,
+ new LinkProperties(mLinkProperties));
+ transitionTo(mStoppingState);
+ }
+ break;
+ }
+
+ // This message is only received when:
+ //
+ // a) initial address acquisition succeeds,
+ // b) renew succeeds or is NAK'd,
+ // c) rebind succeeds or is NAK'd, or
+ // c) the lease expires,
+ //
+ // but never when initial address acquisition fails. The latter
+ // condition is now governed by the provisioning timeout.
+ case DhcpClient.CMD_POST_DHCP_ACTION:
+ stopDhcpAction();
+
+ switch (msg.arg1) {
+ case DhcpClient.DHCP_SUCCESS:
+ handleIPv4Success((DhcpResults) msg.obj);
+ break;
+ case DhcpClient.DHCP_FAILURE:
+ handleIPv4Failure();
+ break;
+ default:
+ logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
+ }
+ break;
+
+ case DhcpClient.CMD_ON_QUIT:
+ // DHCPv4 quit early for some reason.
+ logError("Unexpected CMD_ON_QUIT.");
+ mDhcpClient = null;
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+ }
+
+ private static class MessageHandlingLogger {
+ public String processedInState;
+ public String receivedInState;
+
+ public void reset() {
+ processedInState = null;
+ receivedInState = null;
+ }
+
+ public void handled(State processedIn, IState receivedIn) {
+ processedInState = processedIn.getClass().getSimpleName();
+ receivedInState = receivedIn.getName();
+ }
+
+ public String toString() {
+ return String.format("rcvd_in=%s, proc_in=%s",
+ receivedInState, processedInState);
+ }
+ }
+
+ private static void setNeighborParameters(
+ INetd netd, String ifName, int numSolicits, int interSolicitIntervalMs)
+ throws RemoteException, IllegalArgumentException {
+ Preconditions.checkNotNull(netd);
+ Preconditions.checkArgument(!TextUtils.isEmpty(ifName));
+ Preconditions.checkArgument(numSolicits > 0);
+ Preconditions.checkArgument(interSolicitIntervalMs > 0);
+
+ for (int family : new Integer[]{INetd.IPV4, INetd.IPV6}) {
+ netd.setProcSysNet(family, INetd.NEIGH, ifName, "retrans_time_ms",
+ Integer.toString(interSolicitIntervalMs));
+ netd.setProcSysNet(family, INetd.NEIGH, ifName, "ucast_solicit",
+ Integer.toString(numSolicits));
+ }
+ }
+
+ // TODO: extract out into CollectionUtils.
+ static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
+ for (T t : coll) {
+ if (fn.test(t)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
+ return !any(coll, not(fn));
+ }
+
+ static <T> Predicate<T> not(Predicate<T> fn) {
+ return (t) -> !fn.test(t);
+ }
+
+ static <T> String join(String delimiter, Collection<T> coll) {
+ return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
+ }
+
+ static <T> T find(Iterable<T> coll, Predicate<T> fn) {
+ for (T t: coll) {
+ if (fn.test(t)) {
+ return t;
+ }
+ }
+ return null;
+ }
+
+ static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
+ return coll.stream().filter(fn).collect(Collectors.toList());
+ }
+}
diff --git a/services/net/java/android/net/ip/IpNeighborMonitor.java b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
similarity index 93%
rename from services/net/java/android/net/ip/IpNeighborMonitor.java
rename to packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
index 34bf4b6..eb993a4 100644
--- a/services/net/java/android/net/ip/IpNeighborMonitor.java
+++ b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
@@ -19,6 +19,7 @@
import static android.net.netlink.NetlinkConstants.RTM_DELNEIGH;
import static android.net.netlink.NetlinkConstants.hexify;
import static android.net.netlink.NetlinkConstants.stringForNlMsgType;
+import static android.net.util.SocketUtils.makeNetlinkSocketAddress;
import android.net.MacAddress;
import android.net.netlink.NetlinkErrorMessage;
@@ -31,7 +32,6 @@
import android.os.Handler;
import android.os.SystemClock;
import android.system.ErrnoException;
-import android.system.NetlinkSocketAddress;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
@@ -148,15 +148,12 @@
try {
fd = NetlinkSocket.forProto(OsConstants.NETLINK_ROUTE);
- Os.bind(fd, (SocketAddress)(new NetlinkSocketAddress(0, OsConstants.RTMGRP_NEIGH)));
- Os.connect(fd, (SocketAddress)(new NetlinkSocketAddress(0, 0)));
+ Os.bind(fd, makeNetlinkSocketAddress(0, OsConstants.RTMGRP_NEIGH));
+ NetlinkSocket.connectToKernel(fd);
if (VDBG) {
- final NetlinkSocketAddress nlAddr = (NetlinkSocketAddress) Os.getsockname(fd);
- Log.d(TAG, "bound to sockaddr_nl{"
- + BitUtils.uint32(nlAddr.getPortId()) + ", "
- + nlAddr.getGroupsMask()
- + "}");
+ final SocketAddress nlAddr = Os.getsockname(fd);
+ Log.d(TAG, "bound to sockaddr_nl{" + nlAddr.toString() + "}");
}
} catch (ErrnoException|SocketException e) {
logError("Failed to create rtnetlink socket", e);
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
similarity index 92%
rename from services/net/java/android/net/ip/IpReachabilityMonitor.java
rename to packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
index 8b02156..761db68 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
@@ -22,15 +22,14 @@
import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC;
import android.content.Context;
+import android.net.ConnectivityManager;
import android.net.LinkProperties;
-import android.net.LinkProperties.ProvisioningChange;
import android.net.RouteInfo;
import android.net.ip.IpNeighborMonitor.NeighborEvent;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.IpReachabilityEvent;
import android.net.netlink.StructNdMsg;
import android.net.util.InterfaceParams;
-import android.net.util.MultinetworkPolicyTracker;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.PowerManager;
@@ -166,7 +165,8 @@
private final SharedLog mLog;
private final Callback mCallback;
private final Dependencies mDependencies;
- private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
+ private final boolean mUsingMultinetworkPolicyTracker;
+ private final ConnectivityManager mCm;
private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
private LinkProperties mLinkProperties = new LinkProperties();
private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>();
@@ -175,19 +175,21 @@
public IpReachabilityMonitor(
Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
- MultinetworkPolicyTracker tracker) {
- this(ifParams, h, log, callback, tracker, Dependencies.makeDefault(context, ifParams.name));
+ boolean usingMultinetworkPolicyTracker) {
+ this(context, ifParams, h, log, callback, usingMultinetworkPolicyTracker,
+ Dependencies.makeDefault(context, ifParams.name));
}
@VisibleForTesting
- IpReachabilityMonitor(InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
- MultinetworkPolicyTracker tracker, Dependencies dependencies) {
+ IpReachabilityMonitor(Context context, InterfaceParams ifParams, Handler h, SharedLog log,
+ Callback callback, boolean usingMultinetworkPolicyTracker, Dependencies dependencies) {
if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams");
mInterfaceParams = ifParams;
mLog = log.forSubComponent(TAG);
mCallback = callback;
- mMultinetworkPolicyTracker = tracker;
+ mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker;
+ mCm = context.getSystemService(ConnectivityManager.class);
mDependencies = dependencies;
mIpNeighborMonitor = new IpNeighborMonitor(h, mLog,
@@ -308,10 +310,11 @@
}
}
- final ProvisioningChange delta = LinkProperties.compareProvisioning(
- mLinkProperties, whatIfLp);
+ final boolean lostProvisioning =
+ (mLinkProperties.isIPv4Provisioned() && !whatIfLp.isIPv4Provisioned())
+ || (mLinkProperties.isIPv6Provisioned() && !whatIfLp.isIPv6Provisioned());
- if (delta == ProvisioningChange.LOST_PROVISIONING) {
+ if (lostProvisioning) {
final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
Log.w(TAG, logMsg);
if (mCallback != null) {
@@ -320,11 +323,11 @@
mCallback.notifyLost(ip, logMsg);
}
}
- logNudFailed(delta);
+ logNudFailed(lostProvisioning);
}
private boolean avoidingBadLinks() {
- return (mMultinetworkPolicyTracker == null) || mMultinetworkPolicyTracker.getAvoidBadWifi();
+ return !mUsingMultinetworkPolicyTracker || mCm.getAvoidBadWifi();
}
public void probeAll() {
@@ -370,11 +373,10 @@
mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
}
- private void logNudFailed(ProvisioningChange delta) {
+ private void logNudFailed(boolean lostProvisioning) {
long duration = SystemClock.elapsedRealtime() - mLastProbeTimeMs;
boolean isFromProbe = (duration < getProbeWakeLockDuration());
- boolean isProvisioningLost = (delta == ProvisioningChange.LOST_PROVISIONING);
- int eventType = nudFailureEventType(isFromProbe, isProvisioningLost);
+ int eventType = nudFailureEventType(isFromProbe, lostProvisioning);
mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
}
diff --git a/services/net/java/android/net/util/ConnectivityPacketSummary.java b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
similarity index 79%
rename from services/net/java/android/net/util/ConnectivityPacketSummary.java
rename to packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
index ec833b0..08c3f60 100644
--- a/services/net/java/android/net/util/ConnectivityPacketSummary.java
+++ b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
@@ -16,47 +16,46 @@
package android.net.util;
-import static android.net.util.NetworkConstants.ARP_HWTYPE_ETHER;
-import static android.net.util.NetworkConstants.ARP_PAYLOAD_LEN;
-import static android.net.util.NetworkConstants.ARP_REPLY;
-import static android.net.util.NetworkConstants.ARP_REQUEST;
-import static android.net.util.NetworkConstants.DHCP4_CLIENT_PORT;
-import static android.net.util.NetworkConstants.ETHER_ADDR_LEN;
-import static android.net.util.NetworkConstants.ETHER_DST_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.ETHER_HEADER_LEN;
-import static android.net.util.NetworkConstants.ETHER_SRC_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.ETHER_TYPE_ARP;
-import static android.net.util.NetworkConstants.ETHER_TYPE_IPV4;
-import static android.net.util.NetworkConstants.ETHER_TYPE_IPV6;
-import static android.net.util.NetworkConstants.ETHER_TYPE_OFFSET;
-import static android.net.util.NetworkConstants.ICMPV6_HEADER_MIN_LEN;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_MIN_LENGTH;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_MTU;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_SLLA;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_TLLA;
-import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_SOLICITATION;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION;
-import static android.net.util.NetworkConstants.IPV4_ADDR_LEN;
-import static android.net.util.NetworkConstants.IPV4_DST_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.IPV4_FLAGS_OFFSET;
-import static android.net.util.NetworkConstants.IPV4_FRAGMENT_MASK;
-import static android.net.util.NetworkConstants.IPV4_HEADER_MIN_LEN;
-import static android.net.util.NetworkConstants.IPV4_IHL_MASK;
-import static android.net.util.NetworkConstants.IPV4_PROTOCOL_OFFSET;
-import static android.net.util.NetworkConstants.IPV4_SRC_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.IPV6_ADDR_LEN;
-import static android.net.util.NetworkConstants.IPV6_HEADER_LEN;
-import static android.net.util.NetworkConstants.IPV6_PROTOCOL_OFFSET;
-import static android.net.util.NetworkConstants.IPV6_SRC_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.UDP_HEADER_LEN;
-import static android.net.util.NetworkConstants.asString;
-import static android.net.util.NetworkConstants.asUint;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.IPPROTO_UDP;
+import static com.android.server.util.NetworkStackConstants.ARP_HWTYPE_ETHER;
+import static com.android.server.util.NetworkStackConstants.ARP_PAYLOAD_LEN;
+import static com.android.server.util.NetworkStackConstants.ARP_REPLY;
+import static com.android.server.util.NetworkStackConstants.ARP_REQUEST;
+import static com.android.server.util.NetworkStackConstants.DHCP4_CLIENT_PORT;
+import static com.android.server.util.NetworkStackConstants.ETHER_ADDR_LEN;
+import static com.android.server.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.ETHER_HEADER_LEN;
+import static com.android.server.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_ARP;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV4;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_OFFSET;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MIN_LENGTH;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV4_DST_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV4_FLAGS_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV4_FRAGMENT_MASK;
+import static com.android.server.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV4_IHL_MASK;
+import static com.android.server.util.NetworkStackConstants.IPV4_PROTOCOL_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV4_SRC_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV6_ADDR_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV6_HEADER_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV6_SRC_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.UDP_HEADER_LEN;
+
import android.net.MacAddress;
import android.net.dhcp.DhcpPacket;
@@ -412,4 +411,25 @@
final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x";
return String.format(MAC48_FORMAT, printableBytes);
}
+
+ /**
+ * Convenience method to convert an int to a String.
+ */
+ public static String asString(int i) {
+ return Integer.toString(i);
+ }
+
+ /**
+ * Convenience method to read a byte as an unsigned int.
+ */
+ public static int asUint(byte b) {
+ return (b & 0xff);
+ }
+
+ /**
+ * Convenience method to read a short as an unsigned int.
+ */
+ public static int asUint(short s) {
+ return (s & 0xffff);
+ }
}
diff --git a/services/net/java/android/net/util/FdEventsReader.java b/packages/NetworkStack/src/android/net/util/FdEventsReader.java
similarity index 100%
rename from services/net/java/android/net/util/FdEventsReader.java
rename to packages/NetworkStack/src/android/net/util/FdEventsReader.java
diff --git a/services/net/java/android/net/util/PacketReader.java b/packages/NetworkStack/src/android/net/util/PacketReader.java
similarity index 100%
rename from services/net/java/android/net/util/PacketReader.java
rename to packages/NetworkStack/src/android/net/util/PacketReader.java
diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
index cca71e7..4080ddf 100644
--- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java
+++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
@@ -39,6 +39,8 @@
import android.net.dhcp.DhcpServingParams;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IIpClientCallbacks;
+import android.net.ip.IpClient;
import android.net.shared.PrivateDnsConfig;
import android.net.util.SharedLog;
import android.os.IBinder;
@@ -50,7 +52,11 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
/**
* Android service used to start the network stack when bound to via an intent.
@@ -80,6 +86,8 @@
private static final int NUM_VALIDATION_LOG_LINES = 20;
private final Context mContext;
private final ConnectivityManager mCm;
+ @GuardedBy("mIpClients")
+ private final ArrayList<WeakReference<IpClient>> mIpClients = new ArrayList<>();
private static final int MAX_VALIDATION_LOGS = 10;
@GuardedBy("mValidationLogs")
@@ -138,6 +146,24 @@
}
@Override
+ public void makeIpClient(String ifName, IIpClientCallbacks cb) throws RemoteException {
+ final IpClient ipClient = new IpClient(mContext, ifName, cb);
+
+ synchronized (mIpClients) {
+ final Iterator<WeakReference<IpClient>> it = mIpClients.iterator();
+ while (it.hasNext()) {
+ final IpClient ipc = it.next().get();
+ if (ipc == null) {
+ it.remove();
+ }
+ }
+ mIpClients.add(new WeakReference<>(ipClient));
+ }
+
+ cb.onIpClientCreated(ipClient.makeConnector());
+ }
+
+ @Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {
checkDumpPermission();
@@ -145,6 +171,33 @@
pw.println("NetworkStack logs:");
mLog.dump(fd, pw, args);
+ // Dump full IpClient logs for non-GCed clients
+ pw.println();
+ pw.println("Recently active IpClient logs:");
+ final ArrayList<IpClient> ipClients = new ArrayList<>();
+ final HashSet<String> dumpedIpClientIfaces = new HashSet<>();
+ synchronized (mIpClients) {
+ for (WeakReference<IpClient> ipcRef : mIpClients) {
+ final IpClient ipc = ipcRef.get();
+ if (ipc != null) {
+ ipClients.add(ipc);
+ }
+ }
+ }
+
+ for (IpClient ipc : ipClients) {
+ pw.println(ipc.getName());
+ pw.increaseIndent();
+ ipc.dump(fd, pw, args);
+ pw.decreaseIndent();
+ dumpedIpClientIfaces.add(ipc.getInterfaceName());
+ }
+
+ // State machine and connectivity metrics logs are kept for GCed IpClients
+ pw.println();
+ pw.println("Other IpClient logs:");
+ IpClient.dumpAllLogs(fout, dumpedIpClientIfaces);
+
pw.println();
pw.println("Validation logs (most recent first):");
synchronized (mValidationLogs) {
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index c8a8e1f..6b31b82 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -24,6 +24,7 @@
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE;
@@ -80,7 +81,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Protocol;
import com.android.internal.util.RingBufferIndices;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -150,29 +150,28 @@
}
}
- private static final int BASE = Protocol.BASE_NETWORK_MONITOR;
/**
* ConnectivityService has sent a notification to indicate that network has connected.
* Initiates Network Validation.
*/
- private static final int CMD_NETWORK_CONNECTED = BASE + 1;
+ private static final int CMD_NETWORK_CONNECTED = 1;
/**
* Message to self indicating it's time to evaluate a network's connectivity.
* arg1 = Token to ignore old messages.
*/
- private static final int CMD_REEVALUATE = BASE + 6;
+ private static final int CMD_REEVALUATE = 6;
/**
* ConnectivityService has sent a notification to indicate that network has disconnected.
*/
- private static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
+ private static final int CMD_NETWORK_DISCONNECTED = 7;
/**
* Force evaluation even if it has succeeded in the past.
* arg1 = UID responsible for requesting this reeval. Will be billed for data.
*/
- private static final int CMD_FORCE_REEVALUATION = BASE + 8;
+ private static final int CMD_FORCE_REEVALUATION = 8;
/**
* Message to self indicating captive portal app finished.
@@ -181,7 +180,7 @@
* APP_RETURN_WANTED_AS_IS
* obj = mCaptivePortalLoggedInResponseToken as String
*/
- private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9;
+ private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = 9;
/**
* Message indicating sign-in app should be launched.
@@ -190,14 +189,14 @@
* ConnectivityService when the user touches the "sign into
* network" button in the wifi access point detail page.
*/
- private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11;
+ private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = 11;
/**
* Retest network to see if captive portal is still in place.
* arg1 = UID responsible for requesting this reeval. Will be billed for data.
* 0 indicates self-initiated, so nobody to blame.
*/
- private static final int CMD_CAPTIVE_PORTAL_RECHECK = BASE + 12;
+ private static final int CMD_CAPTIVE_PORTAL_RECHECK = 12;
/**
* ConnectivityService notifies NetworkMonitor of settings changes to
@@ -210,20 +209,20 @@
* states, including being ignored until after an ongoing captive portal
* validation phase is completed.
*/
- private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = BASE + 13;
- private static final int CMD_EVALUATE_PRIVATE_DNS = BASE + 15;
+ private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = 13;
+ private static final int CMD_EVALUATE_PRIVATE_DNS = 15;
/**
* Message to self indicating captive portal detection is completed.
* obj = CaptivePortalProbeResult for detection result;
*/
- public static final int CMD_PROBE_COMPLETE = BASE + 16;
+ public static final int CMD_PROBE_COMPLETE = 16;
/**
* ConnectivityService notifies NetworkMonitor of DNS query responses event.
* arg1 = returncode in OnDnsEvent which indicates the response code for the DNS query.
*/
- public static final int EVENT_DNS_NOTIFICATION = BASE + 17;
+ public static final int EVENT_DNS_NOTIFICATION = 17;
// Start mReevaluateDelayMs at this value and double.
private static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
@@ -245,11 +244,9 @@
private final INetworkMonitorCallbacks mCallback;
private final Network mNetwork;
private final Network mNonPrivateDnsBypassNetwork;
- private final int mNetId;
private final TelephonyManager mTelephonyManager;
private final WifiManager mWifiManager;
private final ConnectivityManager mCm;
- private final NetworkRequest mDefaultRequest;
private final IpConnectivityLog mMetricsLog;
private final Dependencies mDependencies;
@@ -322,7 +319,7 @@
NetworkRequest defaultRequest, IpConnectivityLog logger, SharedLog validationLogs,
Dependencies deps) {
// Add suffix indicating which NetworkMonitor we're talking about.
- super(TAG + "/" + network.netId);
+ super(TAG + "/" + network.toString());
// Logs with a tag of the form given just above, e.g.
// <timestamp> 862 2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ...
@@ -335,11 +332,9 @@
mDependencies = deps;
mNonPrivateDnsBypassNetwork = network;
mNetwork = deps.getPrivateDnsBypassNetwork(network);
- mNetId = mNetwork.netId;
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- mDefaultRequest = defaultRequest;
// CHECKSTYLE:OFF IndentationCheck
addState(mDefaultState);
@@ -471,7 +466,7 @@
@Override
protected void log(String s) {
- if (DBG) Log.d(TAG + "/" + mNetwork.netId, s);
+ if (DBG) Log.d(TAG + "/" + mNetwork.toString(), s);
}
private void validationLog(int probeType, Object url, String msg) {
@@ -489,8 +484,7 @@
}
private boolean isValidationRequired() {
- return NetworkMonitorUtils.isValidationRequired(
- mDefaultRequest.networkCapabilities, mNetworkCapabilities);
+ return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities);
}
@@ -795,7 +789,7 @@
CustomIntentReceiver(String action, int token, int what) {
mToken = token;
mWhat = what;
- mAction = action + "_" + mNetId + "_" + token;
+ mAction = action + "_" + mNetwork.getNetworkHandle() + "_" + token;
mContext.registerReceiver(this, new IntentFilter(mAction));
}
public PendingIntent getPendingIntent() {
@@ -1088,11 +1082,6 @@
return mDependencies.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
}
- private boolean getWifiScansAlwaysAvailableDisabled() {
- return mDependencies.getSetting(
- mContext, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0;
- }
-
private String getCaptivePortalServerHttpsUrl() {
return mDependencies.getSetting(mContext,
Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL);
@@ -1484,7 +1473,7 @@
*/
private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal,
long requestTimestampMs, long responseTimestampMs) {
- if (getWifiScansAlwaysAvailableDisabled()) {
+ if (!mWifiManager.isScanAlwaysAvailable()) {
return;
}
@@ -1568,7 +1557,7 @@
private void logNetworkEvent(int evtype) {
int[] transports = mNetworkCapabilities.getTransportTypes();
- mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype));
+ mMetricsLog.log(mNetwork, transports, new NetworkEvent(evtype));
}
private int networkEventType(ValidationStage s, EvaluationResult r) {
@@ -1590,7 +1579,8 @@
private void maybeLogEvaluationResult(int evtype) {
if (mEvaluationTimer.isRunning()) {
int[] transports = mNetworkCapabilities.getTransportTypes();
- mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype, mEvaluationTimer.stop()));
+ mMetricsLog.log(mNetwork, transports,
+ new NetworkEvent(evtype, mEvaluationTimer.stop()));
mEvaluationTimer.reset();
}
}
@@ -1603,7 +1593,7 @@
.setReturnCode(probeResult)
.setDurationMs(durationMs)
.build();
- mMetricsLog.log(mNetId, transports, ev);
+ mMetricsLog.log(mNetwork, transports, ev);
}
@VisibleForTesting
@@ -1743,7 +1733,7 @@
boolean result = false;
// Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the
// possible traffic cost in metered network.
- if (mNetworkCapabilities.isMetered()
+ if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
&& (SystemClock.elapsedRealtime() - getLastProbeTime()
< mDataStallMinEvaluateTime)) {
return false;
diff --git a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
index bb5900c..eedaf30 100644
--- a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
+++ b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
@@ -32,12 +32,103 @@
public static final int IPV4_MAX_MTU = 65_535;
/**
+ * Ethernet constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc894
+ * - https://tools.ietf.org/html/rfc2464
+ * - https://tools.ietf.org/html/rfc7042
+ * - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml
+ * - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
+ */
+ public static final int ETHER_DST_ADDR_OFFSET = 0;
+ public static final int ETHER_SRC_ADDR_OFFSET = 6;
+ public static final int ETHER_ADDR_LEN = 6;
+ public static final int ETHER_TYPE_OFFSET = 12;
+ public static final int ETHER_TYPE_LENGTH = 2;
+ public static final int ETHER_TYPE_ARP = 0x0806;
+ public static final int ETHER_TYPE_IPV4 = 0x0800;
+ public static final int ETHER_TYPE_IPV6 = 0x86dd;
+ public static final int ETHER_HEADER_LEN = 14;
+
+ /**
+ * ARP constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc826
+ * - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml
+ */
+ public static final int ARP_PAYLOAD_LEN = 28; // For Ethernet+IPv4.
+ public static final int ARP_REQUEST = 1;
+ public static final int ARP_REPLY = 2;
+ public static final int ARP_HWTYPE_RESERVED_LO = 0;
+ public static final int ARP_HWTYPE_ETHER = 1;
+ public static final int ARP_HWTYPE_RESERVED_HI = 0xffff;
+
+ /**
+ * IPv4 constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc791
+ */
+ public static final int IPV4_HEADER_MIN_LEN = 20;
+ public static final int IPV4_IHL_MASK = 0xf;
+ public static final int IPV4_FLAGS_OFFSET = 6;
+ public static final int IPV4_FRAGMENT_MASK = 0x1fff;
+ public static final int IPV4_PROTOCOL_OFFSET = 9;
+ public static final int IPV4_SRC_ADDR_OFFSET = 12;
+ public static final int IPV4_DST_ADDR_OFFSET = 16;
+ public static final int IPV4_ADDR_LEN = 4;
+
+ /**
+ * IPv6 constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc2460
+ */
+ public static final int IPV6_ADDR_LEN = 16;
+ public static final int IPV6_HEADER_LEN = 40;
+ public static final int IPV6_PROTOCOL_OFFSET = 6;
+ public static final int IPV6_SRC_ADDR_OFFSET = 8;
+ public static final int IPV6_DST_ADDR_OFFSET = 24;
+
+ /**
+ * ICMPv6 constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc4443
+ * - https://tools.ietf.org/html/rfc4861
+ */
+ public static final int ICMPV6_HEADER_MIN_LEN = 4;
+ public static final int ICMPV6_ECHO_REPLY_TYPE = 129;
+ public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
+ public static final int ICMPV6_ROUTER_SOLICITATION = 133;
+ public static final int ICMPV6_ROUTER_ADVERTISEMENT = 134;
+ public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135;
+ public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136;
+ public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8;
+ public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8;
+ public static final int ICMPV6_ND_OPTION_SLLA = 1;
+ public static final int ICMPV6_ND_OPTION_TLLA = 2;
+ public static final int ICMPV6_ND_OPTION_MTU = 5;
+
+ /**
+ * UDP constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc768
+ */
+ public static final int UDP_HEADER_LEN = 8;
+
+
+ /**
* DHCP constants.
*
* See also:
* - https://tools.ietf.org/html/rfc2131
*/
public static final int INFINITE_LEASE = 0xffffffff;
+ public static final int DHCP4_CLIENT_PORT = 68;
private NetworkStackConstants() {
throw new UnsupportedOperationException("This class is not to be instantiated");
diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp
index bd7ff2a..45fa2dc 100644
--- a/packages/NetworkStack/tests/Android.bp
+++ b/packages/NetworkStack/tests/Android.bp
@@ -16,9 +16,12 @@
android_test {
name: "NetworkStackTests",
+ certificate: "platform",
srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
static_libs: [
"android-support-test",
+ "frameworks-base-testutils",
"mockito-target-extended-minus-junit4",
"NetworkStackLib",
"testables",
@@ -26,10 +29,70 @@
libs: [
"android.test.runner",
"android.test.base",
+ "android.test.mock",
],
jni_libs: [
// For mockito extended
"libdexmakerjvmtiagent",
"libstaticjvmtiagent",
- ]
-}
\ No newline at end of file
+ // For ApfTest
+ "libartbase",
+ "libbacktrace",
+ "libbase",
+ "libbinder",
+ "libbinderthreadstate",
+ "libc++",
+ "libcrypto",
+ "libcutils",
+ "libdexfile",
+ "libhidl-gen-utils",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
+ "liblog",
+ "liblzma",
+ "libnativehelper",
+ "libnetworkstacktestsjni",
+ "libpackagelistparser",
+ "libpcre2",
+ "libprocessgroup",
+ "libselinux",
+ "libui",
+ "libutils",
+ "libvintf",
+ "libvndksupport",
+ "libtinyxml2",
+ "libunwindstack",
+ "libutilscallstack",
+ "libziparchive",
+ "libz",
+ "netd_aidl_interface-cpp",
+ ],
+}
+
+cc_library_shared {
+ name: "libnetworkstacktestsjni",
+ srcs: [
+ "jni/**/*.cpp"
+ ],
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ ],
+ include_dirs: [
+ "hardware/google/apf",
+ ],
+ shared_libs: [
+ "libbinder",
+ "liblog",
+ "libcutils",
+ "libnativehelper",
+ "netd_aidl_interface-cpp",
+ ],
+ static_libs: [
+ "libapf",
+ "libpcap",
+ ],
+
+}
diff --git a/packages/NetworkStack/tests/AndroidManifest.xml b/packages/NetworkStack/tests/AndroidManifest.xml
index 8b8474f..9cb2c21 100644
--- a/packages/NetworkStack/tests/AndroidManifest.xml
+++ b/packages/NetworkStack/tests/AndroidManifest.xml
@@ -15,6 +15,35 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.server.networkstack.tests">
+
+ <uses-permission android:name="android.permission.READ_LOGS" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+ <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.REAL_GET_TASKS" />
+ <uses-permission android:name="android.permission.GET_DETAILED_TASKS" />
+ <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
+ <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
+ <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
+ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" />
+ <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+ <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
+ <uses-permission android:name="android.permission.NETWORK_STACK" />
+
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tests/net/jni/apf_jni.cpp b/packages/NetworkStack/tests/jni/apf_jni.cpp
similarity index 100%
rename from tests/net/jni/apf_jni.cpp
rename to packages/NetworkStack/tests/jni/apf_jni.cpp
diff --git a/tests/net/res/raw/apf.pcap b/packages/NetworkStack/tests/res/raw/apf.pcap
similarity index 100%
rename from tests/net/res/raw/apf.pcap
rename to packages/NetworkStack/tests/res/raw/apf.pcap
Binary files differ
diff --git a/tests/net/res/raw/apfPcap.pcap b/packages/NetworkStack/tests/res/raw/apfPcap.pcap
similarity index 100%
rename from tests/net/res/raw/apfPcap.pcap
rename to packages/NetworkStack/tests/res/raw/apfPcap.pcap
Binary files differ
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
similarity index 98%
rename from tests/net/java/android/net/apf/ApfTest.java
rename to packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
index 3c3e7ce..f76e412 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
@@ -16,8 +16,6 @@
package android.net.apf;
-import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT;
import static android.system.OsConstants.AF_UNIX;
import static android.system.OsConstants.ARPHRD_ETHER;
import static android.system.OsConstants.ETH_P_ARP;
@@ -28,12 +26,14 @@
import static android.system.OsConstants.SOCK_STREAM;
import static com.android.internal.util.BitUtils.bytesToBEInt;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.content.Context;
@@ -42,10 +42,14 @@
import android.net.apf.ApfFilter.ApfConfiguration;
import android.net.apf.ApfGenerator.IllegalInstructionException;
import android.net.apf.ApfGenerator.Register;
+import android.net.ip.IIpClientCallbacks;
+import android.net.ip.IpClient;
+import android.net.ip.IpClient.IpClientCallbacksWrapper;
import android.net.ip.IpClientCallbacks;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.RaEvent;
import android.net.util.InterfaceParams;
+import android.net.util.SharedLog;
import android.os.ConditionVariable;
import android.os.Parcelable;
import android.os.SystemClock;
@@ -57,8 +61,9 @@
import android.text.format.DateUtils;
import android.util.Log;
-import com.android.frameworks.tests.net.R;
import com.android.internal.util.HexDump;
+import com.android.server.networkstack.tests.R;
+import com.android.server.util.NetworkStackConstants;
import libcore.io.IoUtils;
import libcore.io.Streams;
@@ -100,7 +105,7 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
// Load up native shared library containing APF interpreter exposed via JNI.
- System.loadLibrary("frameworksnettestsjni");
+ System.loadLibrary("networkstacktestsjni");
}
private static final String TAG = "ApfTest";
@@ -915,10 +920,14 @@
HexDump.toHexString(data, false), result);
}
- private class MockIpClientCallback extends IpClientCallbacks {
+ private class MockIpClientCallback extends IpClientCallbacksWrapper {
private final ConditionVariable mGotApfProgram = new ConditionVariable();
private byte[] mLastApfProgram;
+ MockIpClientCallback() {
+ super(mock(IIpClientCallbacks.class), mock(SharedLog.class));
+ }
+
@Override
public void installPacketFilter(byte[] filter) {
mLastApfProgram = filter;
@@ -946,7 +955,7 @@
private final long mFixedTimeMs = SystemClock.elapsedRealtime();
public TestApfFilter(Context context, ApfConfiguration config,
- IpClientCallbacks ipClientCallback, IpConnectivityLog log) throws Exception {
+ IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) throws Exception {
super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, log);
}
@@ -1075,8 +1084,8 @@
private static final byte[] IPV4_ANY_HOST_ADDR = {0, 0, 0, 0};
// Helper to initialize a default apfFilter.
- private ApfFilter setupApfFilter(IpClientCallbacks ipClientCallback, ApfConfiguration config)
- throws Exception {
+ private ApfFilter setupApfFilter(
+ IpClientCallbacksWrapper ipClientCallback, ApfConfiguration config) throws Exception {
LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
LinkProperties lp = new LinkProperties();
lp.addLinkAddress(link);
@@ -1294,7 +1303,7 @@
// However, we should still let through all other ICMPv6 types.
ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone());
- raPacket.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ROUTER_ADVERTISEMENT);
+ raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT);
assertPass(ipClientCallback.getApfProgram(), raPacket.array());
// Now wake up from doze mode to ensure that we no longer drop the packets.
diff --git a/tests/net/java/android/net/apf/Bpf2Apf.java b/packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java
similarity index 100%
rename from tests/net/java/android/net/apf/Bpf2Apf.java
rename to packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java
diff --git a/tests/net/java/android/net/dhcp/DhcpPacketTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
similarity index 100%
rename from tests/net/java/android/net/dhcp/DhcpPacketTest.java
rename to packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
diff --git a/tests/net/java/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
similarity index 89%
rename from tests/net/java/android/net/ip/IpClientTest.java
rename to packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
index a2dcfef..f21809f 100644
--- a/tests/net/java/android/net/ip/IpClientTest.java
+++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
@@ -16,10 +16,13 @@
package android.net.ip;
+import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.eq;
@@ -33,6 +36,7 @@
import android.app.AlarmManager;
import android.content.Context;
import android.content.res.Resources;
+import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.IpPrefix;
import android.net.LinkAddress;
@@ -82,10 +86,11 @@
private static final int TEST_TIMEOUT_MS = 400;
@Mock private Context mContext;
+ @Mock private ConnectivityManager mCm;
@Mock private INetworkManagementService mNMService;
@Mock private INetd mNetd;
@Mock private Resources mResources;
- @Mock private IpClientCallbacks mCb;
+ @Mock private IIpClientCallbacks mCb;
@Mock private AlarmManager mAlarm;
@Mock private IpClient.Dependencies mDependecies;
private MockContentResolver mContentResolver;
@@ -98,6 +103,9 @@
MockitoAnnotations.initMocks(this);
when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm);
+ when(mContext.getSystemServiceName(ConnectivityManager.class))
+ .thenReturn(Context.CONNECTIVITY_SERVICE);
+ when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getInteger(R.integer.config_networkAvoidBadWifi))
.thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE);
@@ -122,13 +130,14 @@
private IpClient makeIpClient(String ifname) throws Exception {
setTestInterfaceParams(ifname);
final IpClient ipc = new IpClient(mContext, ifname, mCb, mDependecies);
- verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).disableIpv6(ifname);
- verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).clearInterfaceAddresses(ifname);
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(ifname, false);
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(ifname);
ArgumentCaptor<BaseNetworkObserver> arg =
ArgumentCaptor.forClass(BaseNetworkObserver.class);
verify(mNMService, times(1)).registerObserver(arg.capture());
mObserver = arg.getValue();
reset(mNMService);
+ reset(mNetd);
// Verify IpClient doesn't call onLinkPropertiesChange() when it starts.
verify(mCb, never()).onLinkPropertiesChange(any());
reset(mCb);
@@ -200,10 +209,11 @@
verify(mCb, never()).onProvisioningFailure(any());
ipc.shutdown();
- verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).disableIpv6(iface);
- verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).clearInterfaceAddresses(iface);
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false);
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface);
verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
- .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface)));
+ .onLinkPropertiesChange(argThat(
+ lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface))));
}
@Test
@@ -248,13 +258,15 @@
mObserver.addressUpdated(iface, new LinkAddress(addresses[lastAddr]));
LinkProperties want = linkproperties(links(addresses), routes(prefixes));
want.setInterfaceName(iface);
- verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(eq(want));
+ verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(argThat(
+ lp -> fromStableParcelable(lp).equals(want)));
ipc.shutdown();
- verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).disableIpv6(iface);
- verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).clearInterfaceAddresses(iface);
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false);
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface);
verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
- .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface)));
+ .onLinkPropertiesChange(argThat(
+ lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface))));
}
@Test
@@ -486,11 +498,11 @@
List<String> list3 = Arrays.asList("bar", "baz");
List<String> list4 = Arrays.asList("foo", "bar", "baz");
- assertTrue(IpClient.all(list1, (x) -> false));
- assertFalse(IpClient.all(list2, (x) -> false));
- assertTrue(IpClient.all(list3, (x) -> true));
- assertTrue(IpClient.all(list2, (x) -> x.charAt(0) == 'f'));
- assertFalse(IpClient.all(list4, (x) -> x.charAt(0) == 'f'));
+ assertTrue(InitialConfiguration.all(list1, (x) -> false));
+ assertFalse(InitialConfiguration.all(list2, (x) -> false));
+ assertTrue(InitialConfiguration.all(list3, (x) -> true));
+ assertTrue(InitialConfiguration.all(list2, (x) -> x.charAt(0) == 'f'));
+ assertFalse(InitialConfiguration.all(list4, (x) -> x.charAt(0) == 'f'));
}
@Test
@@ -500,11 +512,11 @@
List<String> list3 = Arrays.asList("bar", "baz");
List<String> list4 = Arrays.asList("foo", "bar", "baz");
- assertFalse(IpClient.any(list1, (x) -> true));
- assertTrue(IpClient.any(list2, (x) -> true));
- assertTrue(IpClient.any(list2, (x) -> x.charAt(0) == 'f'));
- assertFalse(IpClient.any(list3, (x) -> x.charAt(0) == 'f'));
- assertTrue(IpClient.any(list4, (x) -> x.charAt(0) == 'f'));
+ assertFalse(InitialConfiguration.any(list1, (x) -> true));
+ assertTrue(InitialConfiguration.any(list2, (x) -> true));
+ assertTrue(InitialConfiguration.any(list2, (x) -> x.charAt(0) == 'f'));
+ assertFalse(InitialConfiguration.any(list3, (x) -> x.charAt(0) == 'f'));
+ assertTrue(InitialConfiguration.any(list4, (x) -> x.charAt(0) == 'f'));
}
@Test
diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java
similarity index 90%
rename from tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
rename to packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java
index e65585f..e3b5ddf 100644
--- a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
+++ b/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java
@@ -16,11 +16,10 @@
package android.net.ip;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.when;
+import android.content.Context;
import android.net.util.InterfaceParams;
import android.net.util.SharedLog;
import android.os.Handler;
@@ -45,6 +44,7 @@
@Mock IpReachabilityMonitor.Callback mCallback;
@Mock IpReachabilityMonitor.Dependencies mDependencies;
@Mock SharedLog mLog;
+ @Mock Context mContext;
Handler mHandler;
@Before
@@ -56,7 +56,8 @@
IpReachabilityMonitor makeMonitor() {
final InterfaceParams ifParams = new InterfaceParams("fake0", 1, null);
- return new IpReachabilityMonitor(ifParams, mHandler, mLog, mCallback, null, mDependencies);
+ return new IpReachabilityMonitor(
+ mContext, ifParams, mHandler, mLog, mCallback, false, mDependencies);
}
@Test
diff --git a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java b/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java
similarity index 100%
rename from tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
rename to packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java
diff --git a/tests/net/java/android/net/util/PacketReaderTest.java b/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java
similarity index 100%
rename from tests/net/java/android/net/util/PacketReaderTest.java
rename to packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java
diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
index 9f30eda..716fc8d 100644
--- a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
+++ b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
@@ -33,7 +33,7 @@
android:textAppearance="@style/AppEntitiesHeader.Text.HeaderTitle"/>
<LinearLayout
- android:id="@+id/all_apps_view"
+ android:id="@+id/app_views_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
@@ -61,4 +61,12 @@
android:layout_height="48dp"
android:gravity="center"/>
+ <TextView
+ android:id="@+id/empty_view"
+ android:layout_width="match_parent"
+ android:layout_height="106dp"
+ android:gravity="center"
+ android:visibility="gone"
+ android:textAppearance="@style/AppEntitiesHeader.Text.Summary"/>
+
</LinearLayout>
diff --git a/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
index 73cb8db..330049f 100644
--- a/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
+++ b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
@@ -77,9 +77,11 @@
private final Context mContext;
private final TextView mHeaderTitleView;
+ private final TextView mHeaderEmptyView;
private final Button mHeaderDetailsView;
private final AppEntityInfo[] mAppEntityInfos;
+ private final View mAppViewsContainer;
private final View[] mAppEntityViews;
private final ImageView[] mAppIconViews;
private final TextView[] mAppTitleViews;
@@ -87,6 +89,7 @@
private int mHeaderTitleRes;
private int mHeaderDetailsRes;
+ private int mHeaderEmptyRes;
private View.OnClickListener mDetailsOnClickListener;
/**
@@ -104,6 +107,8 @@
mContext = context;
mHeaderTitleView = appEntitiesHeaderView.findViewById(R.id.header_title);
mHeaderDetailsView = appEntitiesHeaderView.findViewById(R.id.header_details);
+ mHeaderEmptyView = appEntitiesHeaderView.findViewById(R.id.empty_view);
+ mAppViewsContainer = appEntitiesHeaderView.findViewById(R.id.app_views_container);
mAppEntityInfos = new AppEntityInfo[MAXIMUM_APPS];
mAppIconViews = new ImageView[MAXIMUM_APPS];
@@ -152,6 +157,14 @@
}
/**
+ * Sets the string resource id for the empty text.
+ */
+ public AppEntitiesHeaderController setHeaderEmptyRes(@StringRes int emptyRes) {
+ mHeaderEmptyRes = emptyRes;
+ return this;
+ }
+
+ /**
* Set an app entity at a specified position view.
*
* @param index the index at which the specified view is to be inserted
@@ -192,6 +205,12 @@
*/
public void apply() {
bindHeaderTitleView();
+
+ if (isAppEntityInfosEmpty()) {
+ setEmptyViewVisible(true);
+ return;
+ }
+ setEmptyViewVisible(false);
bindHeaderDetailsView();
// Rebind all apps view
@@ -245,4 +264,22 @@
mAppSummaryViews[index].setText(summary);
}
}
+
+ private void setEmptyViewVisible(boolean visible) {
+ if (mHeaderEmptyRes != 0) {
+ mHeaderEmptyView.setText(mHeaderEmptyRes);
+ }
+ mHeaderEmptyView.setVisibility(visible ? View.VISIBLE : View.GONE);
+ mHeaderDetailsView.setVisibility(visible ? View.GONE : View.VISIBLE);
+ mAppViewsContainer.setVisibility(visible ? View.GONE : View.VISIBLE);
+ }
+
+ private boolean isAppEntityInfosEmpty() {
+ for (AppEntityInfo info : mAppEntityInfos) {
+ if (info != null) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 74aaf3c..93f6a94 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -47,6 +47,7 @@
import com.android.internal.widget.LockPatternUtils;
import java.util.List;
+import java.util.Set;
/**
* Utility class to host methods usable in adding a restricted padlock icon and showing admin
@@ -325,7 +326,8 @@
if (admin == null) {
return null;
}
- if (dpm.getCrossProfileCalendarPackages().isEmpty()) {
+ final Set<String> packages = dpm.getCrossProfileCalendarPackages();
+ if (packages != null && packages.isEmpty()) {
return admin;
}
return null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 27dc628..9b12a31 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -622,7 +622,7 @@
.append(KEY_PREFIX_FQDN)
.append(config.FQDN).toString();
} else {
- return getKey(config.SSID, config.BSSID, getSecurity(config));
+ return getKey(removeDoubleQuotes(config.SSID), config.BSSID, getSecurity(config));
}
}
@@ -1555,7 +1555,7 @@
mOsuFailure = mContext.getString(
R.string.osu_failure_provisioning_not_available);
break;
- case OSU_FAILURE_INVALID_SERVER_URL:
+ case OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU:
mOsuFailure = mContext.getString(R.string.osu_failure_invalid_server_url);
break;
case OSU_FAILURE_UNEXPECTED_COMMAND_TYPE:
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
index 8c18c35..4c68c14 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
@@ -60,6 +60,7 @@
.setOnClickListener(v -> {
})
.build();
+ mController.setAppEntity(0, mAppEntityInfo);
}
@Test
@@ -172,6 +173,8 @@
mController.setAppEntity(0, mAppEntityInfo)
.setAppEntity(1, mAppEntityInfo)
.setAppEntity(2, mAppEntityInfo).apply();
+ final View appViewsContainer = mAppEntitiesHeaderView.findViewById(
+ R.id.app_views_container);
final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view);
final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view);
final View app3View = mAppEntitiesHeaderView.findViewById(R.id.app3_view);
@@ -181,8 +184,28 @@
assertThat(app3View.getVisibility()).isEqualTo(View.VISIBLE);
mController.clearAllAppEntities().apply();
- assertThat(app1View.getVisibility()).isEqualTo(View.GONE);
- assertThat(app2View.getVisibility()).isEqualTo(View.GONE);
- assertThat(app3View.getVisibility()).isEqualTo(View.GONE);
+
+ assertThat(appViewsContainer.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void apply_noAppEntitySet_shouldOnlyShowTitleAndEmptyView() {
+ mController.setHeaderTitleRes(R.string.expand_button_title)
+ .setAppEntity(0, mAppEntityInfo)
+ .setAppEntity(1, mAppEntityInfo)
+ .setAppEntity(2, mAppEntityInfo).apply();
+ final View titleView = mAppEntitiesHeaderView.findViewById(R.id.header_title);
+ final View detailsView = mAppEntitiesHeaderView.findViewById(R.id.header_details);
+ final View emptyView = mAppEntitiesHeaderView.findViewById(R.id.empty_view);
+ final View appViewsContainer = mAppEntitiesHeaderView.findViewById(
+ R.id.app_views_container);
+
+ mController.clearAllAppEntities().apply();
+
+ assertThat(titleView.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(emptyView.getVisibility()).isEqualTo(View.VISIBLE);
+
+ assertThat(detailsView.getVisibility()).isEqualTo(View.GONE);
+ assertThat(appViewsContainer.getVisibility()).isEqualTo(View.GONE);
}
}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index de86789..dbeee1c 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -40,11 +40,8 @@
<bool name="def_wifi_display_on">false</bool>
<bool name="def_install_non_market_apps">false</bool>
<bool name="def_package_verifier_enable">true</bool>
- <!-- Comma-separated list of location providers.
- Network location is off by default because it requires
- user opt-in via Setup Wizard or Settings.
- -->
- <string name="def_location_providers_allowed" translatable="false">gps</string>
+ <!-- Comma-separated list of location providers -->
+ <string name="def_location_providers_allowed" translatable="false">gps,network</string>
<bool name="assisted_gps_enabled">true</bool>
<bool name="def_netstats_enabled">true</bool>
<bool name="def_usb_mass_storage_enabled">true</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index e843eb4..850a3c2 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.os.UserHandle;
import android.provider.Settings;
-import android.provider.Settings.Global;
import android.providers.settings.GlobalSettingsProto;
import android.providers.settings.SecureSettingsProto;
import android.providers.settings.SettingProto;
@@ -397,7 +396,7 @@
p.end(certPinToken);
dumpSetting(s, p,
- Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
+ Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
GlobalSettingsProto.CHAINED_BATTERY_ATTRIBUTION_ENABLED);
dumpSetting(s, p,
Settings.Global.COMPATIBILITY_MODE,
@@ -716,6 +715,9 @@
dumpSetting(s, p,
Settings.Global.GUP_BLACKLIST,
GlobalSettingsProto.Gpu.GUP_BLACKLIST);
+ dumpSetting(s, p,
+ Settings.Global.GAME_DRIVER_WHITELIST,
+ GlobalSettingsProto.Gpu.GAME_DRIVER_WHITELIST);
p.end(gpuToken);
final long hdmiToken = p.start(GlobalSettingsProto.HDMI);
@@ -737,7 +739,7 @@
Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
GlobalSettingsProto.HEADS_UP_NOTIFICATIONS_ENABLED);
dumpSetting(s, p,
- Global.HIDDEN_API_BLACKLIST_EXEMPTIONS,
+ Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS,
GlobalSettingsProto.HIDDEN_API_BLACKLIST_EXEMPTIONS);
final long inetCondToken = p.start(GlobalSettingsProto.INET_CONDITION);
@@ -832,6 +834,15 @@
dumpSetting(s, p,
Settings.Global.AUTOMATIC_POWER_SAVER_MODE,
GlobalSettingsProto.LowPowerMode.AUTOMATIC_POWER_SAVER_MODE);
+ dumpSetting(s, p,
+ Settings.Global.LOW_POWER_MODE_STICKY,
+ GlobalSettingsProto.LowPowerMode.STICKY_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
+ GlobalSettingsProto.LowPowerMode.STICKY_AUTO_DISABLE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
+ GlobalSettingsProto.LowPowerMode.STICKY_AUTO_DISABLE_LEVEL);
p.end(lpmToken);
dumpSetting(s, p,
@@ -882,7 +893,7 @@
p.end(multiSimToken);
dumpSetting(s, p,
- Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED,
+ Settings.Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED,
GlobalSettingsProto.NATIVE_FLAGS_HEALTH_CHECK_ENABLED);
final long netstatsToken = p.start(GlobalSettingsProto.NETSTATS);
@@ -1109,9 +1120,6 @@
dumpSetting(s, p,
Settings.Global.POWER_MANAGER_CONSTANTS,
GlobalSettingsProto.POWER_MANAGER_CONSTANTS);
- dumpSetting(s, p,
- Settings.Global.PRIV_APP_OOB_ENABLED,
- GlobalSettingsProto.PRIV_APP_OOB_ENABLED);
final long prepaidSetupToken = p.start(GlobalSettingsProto.PREPAID_SETUP);
dumpSetting(s, p,
@@ -1262,10 +1270,10 @@
final long soundTriggerToken = p.start(GlobalSettingsProto.SOUND_TRIGGER);
dumpSetting(s, p,
- Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
+ Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
GlobalSettingsProto.SoundTrigger.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY);
dumpSetting(s, p,
- Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
+ Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
GlobalSettingsProto.SoundTrigger.DETECTION_SERVICE_OP_TIMEOUT_MS);
p.end(soundTriggerToken);
@@ -1561,7 +1569,7 @@
GlobalSettingsProto.ZRAM_ENABLED);
dumpSetting(s, p,
- Global.APP_OPS_CONSTANTS,
+ Settings.Global.APP_OPS_CONSTANTS,
GlobalSettingsProto.APP_OPS_CONSTANTS);
p.end(token);
@@ -2377,6 +2385,14 @@
Settings.Secure.SILENCE_GESTURE,
SecureSettingsProto.SILENCE_GESTURE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ SecureSettingsProto.THEME_CUSTOMIZATION_OVERLAY_PACKAGES);
+
+ dumpSetting(s, p,
+ Settings.Secure.AWARE_ENABLED,
+ SecureSettingsProto.AWARE_ENABLED);
+
// Please insert new settings using the same order as in SecureSettingsProto.
p.end(token);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 5105ff4..4453121 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3232,7 +3232,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 172;
+ private static final int SETTINGS_VERSION = 173;
private final int mUserId;
@@ -4217,6 +4217,41 @@
currentVersion = 172;
}
+ if (currentVersion == 172) {
+ // Version 172: Set the default value for Secure Settings: LOCATION_MODE
+
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+
+ final Setting locationMode = secureSettings.getSettingLocked(
+ Secure.LOCATION_MODE);
+
+ if (locationMode.isNull()) {
+ final Setting locationProvidersAllowed = secureSettings.getSettingLocked(
+ Secure.LOCATION_PROVIDERS_ALLOWED);
+
+ String defLocationMode = Integer.toString(
+ !TextUtils.isEmpty(locationProvidersAllowed.getValue())
+ ? Secure.LOCATION_MODE_HIGH_ACCURACY
+ : Secure.LOCATION_MODE_OFF);
+ secureSettings.insertSettingLocked(
+ Secure.LOCATION_MODE, defLocationMode,
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+
+ // also reset LOCATION_PROVIDERS_ALLOWED back to the default value - this
+ // setting is now only for debug/test purposes, and will likely be removed
+ // in a later release. LocationManagerService is responsible for adjusting
+ // these settings to the proper state.
+
+ String defLocationProvidersAllowed = getContext().getResources().getString(
+ R.string.def_location_providers_allowed);
+ secureSettings.insertSettingLocked(
+ Secure.LOCATION_PROVIDERS_ALLOWED, defLocationProvidersAllowed,
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+
+ currentVersion = 173;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b903142..c3c3f25 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -166,6 +166,8 @@
<uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
<uses-permission android:name="android.permission.SUSPEND_APPS" />
<uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
+ <!-- Permission needed to wipe the device for Test Harness Mode -->
+ <uses-permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE" />
<uses-permission android:name="android.permission.MANAGE_APPOPS" />
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 2d7471d..a9ff21f 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -1962,8 +1962,7 @@
}
@Override
- public void onFinished(long durationMs, String title, String description)
- throws RemoteException {
+ public void onFinished() throws RemoteException {
// TODO(b/111441001): implement
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b4f2711..3453e79 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -386,6 +386,15 @@
android:excludeFromRecents="true">
</activity>
+ <!-- started from UsbPortManager -->
+ <activity android:name=".usb.UsbContaminantActivity"
+ android:exported="true"
+ android:permission="android.permission.MANAGE_USB"
+ android:theme="@style/Theme.SystemUI.Dialog.Alert"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true">
+ </activity>
+
<!-- started from AdbDebuggingManager -->
<activity android:name=".usb.UsbDebuggingActivity"
android:permission="android.permission.MANAGE_DEBUGGING"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 98f0cbe..f2be2e7 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -458,6 +458,11 @@
heads-up notifications. -->
<bool name="config_smart_replies_in_notifications_show_in_heads_up">true</bool>
+ <!-- Smart replies in notifications: Minimum number of system generated smart replies that
+ should be shown in a notification. If we cannot show at least this many replies we instead
+ show none. -->
+ <integer name="config_smart_replies_in_notifications_min_num_system_generated_replies">0</integer>
+
<!-- Screenshot editing default activity. Must handle ACTION_EDIT image/png intents.
Blank sends the user to the Chooser first.
This name is in the ComponentName flattened format (package/class) -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f384d8f..190bd7a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -150,12 +150,21 @@
<!-- Option to always allow USB debugging from the attached computer -->
<string name="usb_debugging_always">Always allow from this computer</string>
+ <!-- Button label for confirming acceptance of enabling USB debugging [CHAR LIMIT=15] -->
+ <string name="usb_debugging_allow">Allow</string>
+
<!-- Title of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. -->
<string name="usb_debugging_secondary_user_title">USB debugging not allowed</string>
<!-- Message of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. -->
<string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user.</string>
+ <!-- Title of USB contaminant presence dialog [CHAR LIMIT=NONE] -->
+ <string name="usb_contaminant_title">USB port disabled</string>
+
+ <!-- Message of USB contaminant presence dialog [CHAR LIMIT=NONE] -->
+ <string name="usb_contaminant_message">To protect your device from liquid or debris, the USB port is disabled and won\u2019t detect any accessories.\n\nYou\u2019ll be notified when it\u2019s safe to use the USB port again.</string>
+
<!-- Checkbox label for application compatibility mode ON (zooming app to look like it's running
on a phone). [CHAR LIMIT=25] -->
<string name="compat_mode_on">Zoom to fill screen</string>
@@ -2276,7 +2285,7 @@
app for debugging. Will not be seen by users. [CHAR LIMIT=20] -->
<string name="heap_dump_tile_name">Dump SysUI Heap</string>
- <!-- Text on chip for multiple apps using a single app op [CHAR LIMIT=10] -->
+ <!-- Text on chip for multiple apps using a single app op [CHAR LIMIT=12] -->
<plurals name="ongoing_privacy_chip_multiple_apps">
<item quantity="one"><xliff:g id="num_apps" example="1">%d</xliff:g> app</item>
<item quantity="few"><xliff:g id="num_apps" example="3">%d</xliff:g> apps</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 1aff394..7218acf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,15 +1,9 @@
package com.android.keyguard;
-import android.content.ContentResolver;
import android.content.Context;
-import android.database.ContentObserver;
import android.graphics.Paint;
import android.graphics.Paint.Style;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
import android.util.AttributeSet;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -18,29 +12,19 @@
import androidx.annotation.VisibleForTesting;
-import com.android.keyguard.clock.BubbleClockController;
-import com.android.keyguard.clock.StretchAnalogClockController;
-import com.android.keyguard.clock.TypeClockController;
+import com.android.keyguard.clock.ClockManager;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.ExtensionController.Extension;
-import java.util.Objects;
import java.util.TimeZone;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
*/
public class KeyguardClockSwitch extends RelativeLayout {
- private LayoutInflater mLayoutInflater;
-
- private final ContentResolver mContentResolver;
/**
* Optional/alternative clock injected via plugin.
*/
@@ -63,14 +47,6 @@
*/
private View mKeyguardStatusArea;
/**
- * Used to select between plugin or default implementations of ClockPlugin interface.
- */
- private Extension<ClockPlugin> mClockExtension;
- /**
- * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads.
- */
- private final Consumer<ClockPlugin> mClockPluginConsumer = plugin -> setClockPlugin(plugin);
- /**
* Maintain state so that a newly connected plugin can be initialized.
*/
private float mDarkAmount;
@@ -94,16 +70,7 @@
}
};
- private final ContentObserver mContentObserver =
- new ContentObserver(new Handler(Looper.getMainLooper())) {
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
- if (mClockExtension != null) {
- mClockExtension.reload();
- }
- }
- };
+ private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
public KeyguardClockSwitch(Context context) {
this(context, null);
@@ -111,8 +78,6 @@
public KeyguardClockSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
- mLayoutInflater = LayoutInflater.from(context);
- mContentResolver = context.getContentResolver();
}
/**
@@ -133,45 +98,14 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- mClockExtension = Dependency.get(ExtensionController.class).newExtension(ClockPlugin.class)
- .withPlugin(ClockPlugin.class)
- .withCallback(mClockPluginConsumer)
- // Using withDefault even though this isn't the default as a workaround.
- // ExtensionBulider doesn't provide the ability to supply a ClockPlugin
- // instance based off of the value of a setting. Since multiple "default"
- // can be provided, using a supplier that changes the settings value.
- // A null return will cause Extension#reload to look at the next "default"
- // supplier.
- .withDefault(
- new SettingsGattedSupplier(
- mContentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
- BubbleClockController.class.getName(),
- () -> BubbleClockController.build(mLayoutInflater)))
- .withDefault(
- new SettingsGattedSupplier(
- mContentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
- StretchAnalogClockController.class.getName(),
- () -> StretchAnalogClockController.build(mLayoutInflater)))
- .withDefault(
- new SettingsGattedSupplier(
- mContentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
- TypeClockController.class.getName(),
- () -> TypeClockController.build(mLayoutInflater)))
- .build();
- mContentResolver.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
- false, mContentObserver);
+ Dependency.get(ClockManager.class).addOnClockChangedListener(mClockChangedListener);
Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mClockExtension.destroy();
- mContentResolver.unregisterContentObserver(mContentObserver);
+ Dependency.get(ClockManager.class).removeOnClockChangedListener(mClockChangedListener);
Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
}
@@ -313,52 +247,12 @@
}
@VisibleForTesting (otherwise = VisibleForTesting.NONE)
- Consumer<ClockPlugin> getClockPluginConsumer() {
- return mClockPluginConsumer;
+ ClockManager.ClockChangedListener getClockChangedListener() {
+ return mClockChangedListener;
}
@VisibleForTesting (otherwise = VisibleForTesting.NONE)
StatusBarStateController.StateListener getStateListener() {
return mStateListener;
}
-
- /**
- * Supplier that only gets an instance when a settings value matches expected value.
- */
- private static class SettingsGattedSupplier implements Supplier<ClockPlugin> {
-
- private final ContentResolver mContentResolver;
- private final String mKey;
- private final String mValue;
- private final Supplier<ClockPlugin> mSupplier;
-
- /**
- * Constructs a supplier that changes secure setting key against value.
- *
- * @param contentResolver Used to look up settings value.
- * @param key Settings key.
- * @param value If the setting matches this values that get supplies a ClockPlugin
- * instance.
- * @param supplier Supplier of ClockPlugin instance, only used if the setting
- * matches value.
- */
- SettingsGattedSupplier(ContentResolver contentResolver, String key, String value,
- Supplier<ClockPlugin> supplier) {
- mContentResolver = contentResolver;
- mKey = key;
- mValue = value;
- mSupplier = supplier;
- }
-
- /**
- * Returns null if the settings value doesn't match the expected value.
- *
- * A null return causes Extension#reload to skip this supplier and move to the next.
- */
- @Override
- public ClockPlugin get() {
- final String currentValue = Settings.Secure.getString(mContentResolver, mKey);
- return Objects.equals(currentValue, mValue) ? mSupplier.get() : null;
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
new file mode 100644
index 0000000..3217ca6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -0,0 +1,202 @@
+/*
+ * 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.keyguard.clock;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+
+import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.statusbar.policy.ExtensionController;
+import com.android.systemui.statusbar.policy.ExtensionController.Extension;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Manages custom clock faces.
+ */
+@Singleton
+public final class ClockManager {
+
+ private final LayoutInflater mLayoutInflater;
+ private final ContentResolver mContentResolver;
+
+ /**
+ * Observe settings changes to know when to switch the clock face.
+ */
+ private final ContentObserver mContentObserver =
+ new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ if (mClockExtension != null) {
+ mClockExtension.reload();
+ }
+ }
+ };
+
+ private final ExtensionController mExtensionController;
+ /**
+ * Used to select between plugin or default implementations of ClockPlugin interface.
+ */
+ private Extension<ClockPlugin> mClockExtension;
+ /**
+ * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads.
+ */
+ private final Consumer<ClockPlugin> mClockPluginConsumer = this::setClockPlugin;
+
+ private final List<ClockChangedListener> mListeners = new ArrayList<>();
+
+ @Inject
+ public ClockManager(Context context, ExtensionController extensionController) {
+ mExtensionController = extensionController;
+ mLayoutInflater = LayoutInflater.from(context);
+ mContentResolver = context.getContentResolver();
+ }
+
+ /**
+ * Add listener to be notified when clock implementation should change.
+ */
+ public void addOnClockChangedListener(ClockChangedListener listener) {
+ if (mListeners.isEmpty()) {
+ register();
+ }
+ mListeners.add(listener);
+ if (mClockExtension != null) {
+ mClockExtension.reload();
+ }
+ }
+
+ /**
+ * Remove listener added with {@link addOnClockChangedListener}.
+ */
+ public void removeOnClockChangedListener(ClockChangedListener listener) {
+ mListeners.remove(listener);
+ if (mListeners.isEmpty()) {
+ unregister();
+ }
+ }
+
+ private void setClockPlugin(ClockPlugin plugin) {
+ for (int i = 0; i < mListeners.size(); i++) {
+ // It probably doesn't make sense to supply the same plugin instances to multiple
+ // listeners. This should be fine for now since there is only a single listener.
+ mListeners.get(i).onClockChanged(plugin);
+ }
+ }
+
+ private void register() {
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+ false, mContentObserver);
+ mClockExtension = mExtensionController.newExtension(ClockPlugin.class)
+ .withPlugin(ClockPlugin.class)
+ .withCallback(mClockPluginConsumer)
+ // Using withDefault even though this isn't the default as a workaround.
+ // ExtensionBuilder doesn't provide the ability to supply a ClockPlugin
+ // instance based off of the value of a setting. Since multiple "default"
+ // can be provided, using a supplier that changes the settings value.
+ // A null return will cause Extension#reload to look at the next "default"
+ // supplier.
+ .withDefault(
+ new SettingsGattedSupplier(
+ mContentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+ BubbleClockController.class.getName(),
+ () -> BubbleClockController.build(mLayoutInflater)))
+ .withDefault(
+ new SettingsGattedSupplier(
+ mContentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+ StretchAnalogClockController.class.getName(),
+ () -> StretchAnalogClockController.build(mLayoutInflater)))
+ .withDefault(
+ new SettingsGattedSupplier(
+ mContentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+ TypeClockController.class.getName(),
+ () -> TypeClockController.build(mLayoutInflater)))
+ .build();
+ }
+
+ private void unregister() {
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ mClockExtension.destroy();
+ }
+
+ /**
+ * Listener for events that should cause the custom clock face to change.
+ */
+ public interface ClockChangedListener {
+ /**
+ * Called when custom clock should change.
+ *
+ * @param clock Custom clock face to use. A null value indicates the default clock face.
+ */
+ void onClockChanged(ClockPlugin clock);
+ }
+
+ /**
+ * Supplier that only gets an instance when a settings value matches expected value.
+ */
+ private static class SettingsGattedSupplier implements Supplier<ClockPlugin> {
+
+ private final ContentResolver mContentResolver;
+ private final String mKey;
+ private final String mValue;
+ private final Supplier<ClockPlugin> mSupplier;
+
+ /**
+ * Constructs a supplier that changes secure setting key against value.
+ *
+ * @param contentResolver Used to look up settings value.
+ * @param key Settings key.
+ * @param value If the setting matches this values that get supplies a ClockPlugin
+ * instance.
+ * @param supplier Supplier of ClockPlugin instance, only used if the setting
+ * matches value.
+ */
+ SettingsGattedSupplier(ContentResolver contentResolver, String key, String value,
+ Supplier<ClockPlugin> supplier) {
+ mContentResolver = contentResolver;
+ mKey = key;
+ mValue = value;
+ mSupplier = supplier;
+ }
+
+ /**
+ * Returns null if the settings value doesn't match the expected value.
+ *
+ * A null return causes Extension#reload to skip this supplier and move to the next.
+ */
+ @Override
+ public ClockPlugin get() {
+ final String currentValue = Settings.Secure.getString(mContentResolver, mKey);
+ return Objects.equals(currentValue, mValue) ? mSupplier.get() : null;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index ec6ecc6..d99f234 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -29,6 +29,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.Preconditions;
+import com.android.keyguard.clock.ClockManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.assist.AssistManager;
@@ -283,6 +284,7 @@
@Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
@Nullable
@Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
+ @Inject Lazy<ClockManager> mClockManager;
@Inject
public Dependency() {
@@ -449,6 +451,7 @@
mProviders.put(NotificationAlertingManager.class, mNotificationAlertingManager::get);
mProviders.put(ForegroundServiceNotificationListener.class,
mForegroundServiceNotificationListener::get);
+ mProviders.put(ClockManager.class, mClockManager::get);
// TODO(b/118592525): to support multi-display , we start to add something which is
// per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 1d2d7fa..2aecc24 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -16,14 +16,18 @@
package com.android.systemui;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import android.app.WallpaperManager;
import android.content.ComponentCallbacks2;
+import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region.Op;
+import android.hardware.display.DisplayManager;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Trace;
@@ -33,7 +37,6 @@
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceHolder;
-import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -94,7 +97,7 @@
float mYOffset = 0f;
float mScale = 1f;
- private Display mDefaultDisplay;
+ private Display mDisplay;
private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
boolean mVisible = true;
@@ -138,10 +141,20 @@
super.onCreate(surfaceHolder);
//noinspection ConstantConditions
- mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
+ final Context displayContext = getDisplayContext();
+ final int displayId = displayContext == null ? DEFAULT_DISPLAY :
+ displayContext.getDisplayId();
+ DisplayManager dm = getSystemService(DisplayManager.class);
+ if (dm != null) {
+ mDisplay = dm.getDisplay(displayId);
+ if (mDisplay == null) {
+ Log.e(TAG, "Cannot find display! Fallback to default.");
+ mDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+ }
+ }
setOffsetNotificationsEnabled(false);
- updateSurfaceSize(surfaceHolder, getDefaultDisplayInfo(), false /* forDraw */);
+ updateSurfaceSize(surfaceHolder, getDisplayInfo(), false /* forDraw */);
}
@Override
@@ -165,9 +178,26 @@
hasWallpaper = false;
}
- // Set surface size equal to bitmap size, prevent memory waste
- int surfaceWidth = Math.max(MIN_BACKGROUND_WIDTH, mBackgroundWidth);
- int surfaceHeight = Math.max(MIN_BACKGROUND_HEIGHT, mBackgroundHeight);
+ // Expected surface size.
+ int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth);
+ int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight);
+
+ // Calculate the minimum drawing area of the surface, which saves memory and does not
+ // distort the image.
+ final float scale = Math.min(
+ (float) mBackgroundHeight / (float) surfaceHeight,
+ (float) mBackgroundWidth / (float) surfaceWidth);
+ surfaceHeight = (int) (scale * surfaceHeight);
+ surfaceWidth = (int) (scale * surfaceWidth);
+
+ // Set surface size to at least MIN size.
+ if (surfaceWidth < MIN_BACKGROUND_WIDTH || surfaceHeight < MIN_BACKGROUND_HEIGHT) {
+ final float scaleUp = Math.max(
+ (float) MIN_BACKGROUND_WIDTH / (float) surfaceWidth,
+ (float) MIN_BACKGROUND_HEIGHT / (float) surfaceHeight);
+ surfaceWidth = (int) ((float) surfaceWidth * scaleUp);
+ surfaceHeight = (int) ((float) surfaceHeight * scaleUp);
+ }
// Used a fixed size surface, because we are special. We can do
// this because we know the current design of window animations doesn't
@@ -267,8 +297,8 @@
}
@VisibleForTesting
- DisplayInfo getDefaultDisplayInfo() {
- mDefaultDisplay.getDisplayInfo(mTmpDisplayInfo);
+ DisplayInfo getDisplayInfo() {
+ mDisplay.getDisplayInfo(mTmpDisplayInfo);
return mTmpDisplayInfo;
}
@@ -278,7 +308,7 @@
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawWallpaper");
- DisplayInfo displayInfo = getDefaultDisplayInfo();
+ DisplayInfo displayInfo = getDisplayInfo();
int newRotation = displayInfo.rotation;
// Sometimes a wallpaper is not large enough to cover the screen in one dimension.
@@ -445,7 +475,7 @@
if (DEBUG) {
Log.d(TAG, "Wallpaper loaded: " + mBackground);
}
- updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),
+ updateSurfaceSize(getSurfaceHolder(), getDisplayInfo(),
false /* forDraw */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 3666400..9efa656 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -604,8 +604,14 @@
if (absDelta >= size) {
delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
} else {
- delta = maxScrollDistance * (float) Math.sin(
- (delta / size) * (Math.PI / 2));
+ int startPosition = mCallback.getConstrainSwipeStartPosition();
+ if (absDelta > startPosition) {
+ int signedStartPosition =
+ (int) (startPosition * Math.signum(delta));
+ delta = signedStartPosition
+ + maxScrollDistance * (float) Math.sin(
+ ((delta - signedStartPosition) / size) * (Math.PI / 2));
+ }
}
}
@@ -742,6 +748,14 @@
float getFalsingThresholdFactor();
/**
+ * @return The position, in pixels, at which a constrained swipe should start being
+ * constrained.
+ */
+ default int getConstrainSwipeStartPosition() {
+ return 0;
+ }
+
+ /**
* @return If true, the given view is draggable.
*/
default boolean canChildBeDragged(@NonNull View animView) { return true; }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 53cdee5..4d70890 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -47,6 +47,10 @@
public class AssistManager implements ConfigurationChangedReceiver {
private static final String TAG = "AssistManager";
+
+ // Note that VERBOSE logging may leak PII (e.g. transcription contents).
+ private static final boolean VERBOSE = false;
+
private static final String ASSIST_ICON_METADATA_NAME =
"com.android.systemui.action_assist_icon";
@@ -103,16 +107,41 @@
protected void registerVoiceInteractionSessionListener() {
mAssistUtils.registerVoiceInteractionSessionListener(
new IVoiceInteractionSessionListener.Stub() {
- @Override
- public void onVoiceSessionShown() throws RemoteException {
- Log.v(TAG, "Voice open");
- }
+ @Override
+ public void onVoiceSessionShown() throws RemoteException {
+ if (VERBOSE) {
+ Log.v(TAG, "Voice open");
+ }
+ }
- @Override
- public void onVoiceSessionHidden() throws RemoteException {
- Log.v(TAG, "Voice closed");
- }
- });
+ @Override
+ public void onVoiceSessionHidden() throws RemoteException {
+ if (VERBOSE) {
+ Log.v(TAG, "Voice closed");
+ }
+ }
+
+ @Override
+ public void onTranscriptionUpdate(String transcription) {
+ if (VERBOSE) {
+ Log.v(TAG, "Transcription Updated: \"" + transcription + "\"");
+ }
+ }
+
+ @Override
+ public void onTranscriptionComplete(boolean immediate) {
+ if (VERBOSE) {
+ Log.v(TAG, "Transcription complete (immediate=" + immediate + ")");
+ }
+ }
+
+ @Override
+ public void onVoiceStateChange(int state) {
+ if (VERBOSE) {
+ Log.v(TAG, "Voice state is now " + state);
+ }
+ }
+ });
}
public void onConfigurationChanged(Configuration newConfiguration) {
@@ -291,8 +320,10 @@
}
}
} catch (PackageManager.NameNotFoundException e) {
- Log.v(TAG, "Assistant component "
- + component.flattenToShortString() + " not found");
+ if (VERBOSE) {
+ Log.v(TAG, "Assistant component "
+ + component.flattenToShortString() + " not found");
+ }
} catch (Resources.NotFoundException nfe) {
Log.w(TAG, "Failed to swap drawable from "
+ component.flattenToShortString(), nfe);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 957d772..a457dee 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -267,8 +267,9 @@
BubbleView bubble = (BubbleView) mInflater.inflate(
R.layout.bubble_view, mStackView, false /* attachToRoot */);
bubble.setNotif(notif);
- if (shouldUseActivityView(mContext)) {
- bubble.setAppOverlayIntent(getAppOverlayIntent(notif));
+ PendingIntent bubbleIntent = getValidBubbleIntent(notif);
+ if (shouldUseActivityView(mContext) || bubbleIntent != null) {
+ bubble.setBubbleIntent(getValidBubbleIntent(notif));
}
mBubbles.put(bubble.getKey(), bubble);
mStackView.addBubble(bubble);
@@ -282,7 +283,7 @@
}
@Nullable
- private PendingIntent getAppOverlayIntent(NotificationEntry notif) {
+ private PendingIntent getValidBubbleIntent(NotificationEntry notif) {
Notification notification = notif.notification.getNotification();
if (canLaunchInActivityView(notification.getBubbleMetadata() != null
? notification.getBubbleMetadata().getIntent() : null)) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 9a11b96..dcd121b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -38,9 +38,11 @@
import android.view.animation.AccelerateInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -226,6 +228,19 @@
}
/**
+ * Sets the entry that should be expanded and expands if needed.
+ */
+ @VisibleForTesting
+ public void setExpandedBubble(NotificationEntry entry) {
+ for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
+ BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
+ if (entry.equals(bv.getEntry())) {
+ setExpandedBubble(bv);
+ }
+ }
+ }
+
+ /**
* Adds a bubble to the top of the stack.
*
* @param bubbleView the view to add to the stack.
@@ -456,7 +471,8 @@
if (mExpandedBubble.hasAppOverlayIntent()) {
// Bubble with activity view expanded state
ActivityView expandedView = mExpandedBubble.getActivityView();
- expandedView.setLayoutParams(new ViewGroup.LayoutParams(
+ // XXX: gets added to linear layout
+ expandedView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight));
final PendingIntent intent = mExpandedBubble.getAppOverlayIntent();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 91893ef..e8432b9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -33,6 +33,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.graphics.ColorUtils;
@@ -251,7 +252,7 @@
// HACK: release() will crash if the view is not attached.
mActivityView.setVisibility(View.GONE);
- tmpParent.addView(mActivityView, new ViewGroup.LayoutParams(
+ tmpParent.addView(mActivityView, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
}
@@ -298,7 +299,7 @@
}
- public void setAppOverlayIntent(PendingIntent intent) {
+ public void setBubbleIntent(PendingIntent intent) {
mAppOverlayIntent = intent;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dock/DockManager.java b/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
index fa5a114..d332f59 100644
--- a/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
+++ b/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
@@ -48,6 +48,11 @@
*/
void removeListener(DockEventListener callback);
+ /**
+ * Returns true if the device is in docking state.
+ */
+ boolean isDocked();
+
/** Callback for receiving dock events */
interface DockEventListener {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
index 9fc2234..5353ee6 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
@@ -22,7 +22,6 @@
import android.util.Log;
import com.android.internal.hardware.AmbientDisplayConfiguration;
-import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeMachine.State;
@@ -46,12 +45,12 @@
private int mDockState = DockManager.STATE_NONE;
public DozeDockHandler(Context context, DozeMachine machine, DozeHost dozeHost,
- AmbientDisplayConfiguration config, Handler handler) {
+ AmbientDisplayConfiguration config, Handler handler, DockManager dockManager) {
mMachine = machine;
mDozeHost = dozeHost;
mConfig = config;
mHandler = handler;
- mDockManager = SysUiServiceProvider.getComponent(context, DockManager.class);
+ mDockManager = dockManager;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 58ae555..e338a34 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -27,8 +27,10 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.dock.DockManager;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.AsyncSensorManager;
import com.android.systemui.util.wakelock.DelayedWakeLock;
@@ -44,6 +46,7 @@
Context context = dozeService;
SensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
+ DockManager dockManager = SysUiServiceProvider.getComponent(context, DockManager.class);
DozeHost host = getHost(dozeService);
AmbientDisplayConfiguration config = new AmbientDisplayConfiguration(context);
@@ -63,13 +66,13 @@
new DozePauser(handler, machine, alarmManager, params.getPolicy()),
new DozeFalsingManagerAdapter(FalsingManager.getInstance(context)),
createDozeTriggers(context, sensorManager, host, alarmManager, config, params,
- handler, wakeLock, machine),
+ handler, wakeLock, machine, dockManager),
createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params),
new DozeScreenState(wrappedService, handler, params, wakeLock),
createDozeScreenBrightness(context, wrappedService, sensorManager, host, params,
handler),
new DozeWallpaperState(context),
- new DozeDockHandler(context, machine, host, config, handler)
+ new DozeDockHandler(context, machine, host, config, handler, dockManager)
});
return machine;
@@ -86,10 +89,11 @@
private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager,
DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config,
- DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine) {
+ DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine,
+ DockManager dockManager) {
boolean allowPulseTriggers = true;
return new DozeTriggers(context, machine, host, alarmManager, config, params,
- sensorManager, handler, wakeLock, allowPulseTriggers);
+ sensorManager, handler, wakeLock, allowPulseTriggers, dockManager);
}
private DozeMachine.Part createDozeUi(Context context, DozeHost host, WakeLock wakeLock,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 78374a0..562edd6 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -214,6 +214,15 @@
mPickupSensor.setDisabled(disable);
}
+ /** Ignore the setting value of only the sensors that require the touchscreen. */
+ public void ignoreTouchScreenSensorsSettingInterferingWithDocking(boolean ignore) {
+ for (TriggerSensor sensor : mSensors) {
+ if (sensor.mRequiresTouchscreen) {
+ sensor.ignoreSetting(ignore);
+ }
+ }
+ }
+
/** Dump current state */
public void dump(PrintWriter pw) {
for (TriggerSensor s : mSensors) {
@@ -323,6 +332,7 @@
protected boolean mRequested;
protected boolean mRegistered;
protected boolean mDisabled;
+ protected boolean mIgnoresSetting;
public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
boolean reportsTouchCoordinates, boolean requiresTouchscreen) {
@@ -333,6 +343,13 @@
public TriggerSensor(Sensor sensor, String setting, boolean settingDef,
boolean configured, int pulseReason, boolean reportsTouchCoordinates,
boolean requiresTouchscreen) {
+ this(sensor, setting, settingDef, configured, pulseReason, reportsTouchCoordinates,
+ requiresTouchscreen, false /* ignoresSetting */);
+ }
+
+ private TriggerSensor(Sensor sensor, String setting, boolean settingDef,
+ boolean configured, int pulseReason, boolean reportsTouchCoordinates,
+ boolean requiresTouchscreen, boolean ignoresSetting) {
mSensor = sensor;
mSetting = setting;
mSettingDefault = settingDef;
@@ -340,6 +357,7 @@
mPulseReason = pulseReason;
mReportsTouchCoordinates = reportsTouchCoordinates;
mRequiresTouchscreen = requiresTouchscreen;
+ mIgnoresSetting = ignoresSetting;
}
public void setListening(boolean listen) {
@@ -354,9 +372,16 @@
updateListener();
}
+ public void ignoreSetting(boolean ignored) {
+ if (mIgnoresSetting == ignored) return;
+ mIgnoresSetting = ignored;
+ updateListener();
+ }
+
public void updateListener() {
if (!mConfigured || mSensor == null) return;
- if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) {
+ if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)
+ && !mRegistered) {
mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered);
} else if (mRegistered) {
@@ -382,6 +407,7 @@
.append(", mRequested=").append(mRequested)
.append(", mDisabled=").append(mDisabled)
.append(", mConfigured=").append(mConfigured)
+ .append(", mIgnoresSetting=").append(mIgnoresSetting)
.append(", mSensor=").append(mSensor).append("}").toString();
}
@@ -464,7 +490,8 @@
public void updateListener() {
if (!mConfigured) return;
AsyncSensorManager asyncSensorManager = (AsyncSensorManager) mSensorManager;
- if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) {
+ if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)
+ && !mRegistered) {
asyncSensorManager.registerPluginListener(mPluginSensor, mTriggerEventListener);
mRegistered = true;
if (DEBUG) Log.d(TAG, "registerPluginListener");
@@ -481,6 +508,7 @@
.append(", mRequested=").append(mRequested)
.append(", mDisabled=").append(mDisabled)
.append(", mConfigured=").append(mConfigured)
+ .append(", mIgnoresSetting=").append(mIgnoresSetting)
.append(", mSensor=").append(mPluginSensor).append("}").toString();
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index e2e448b..dc505b5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -33,8 +33,10 @@
import android.text.format.Formatter;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.internal.util.Preconditions;
+import com.android.systemui.dock.DockManager;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.Assert;
import com.android.systemui.util.wakelock.WakeLock;
@@ -71,6 +73,8 @@
private final boolean mAllowPulseTriggers;
private final UiModeManager mUiModeManager;
private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver();
+ private final DockEventListener mDockEventListener = new DockEventListener();
+ private final DockManager mDockManager;
private long mNotificationPulseTime;
private boolean mPulsePending;
@@ -79,7 +83,7 @@
public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost,
AlarmManager alarmManager, AmbientDisplayConfiguration config,
DozeParameters dozeParameters, SensorManager sensorManager, Handler handler,
- WakeLock wakeLock, boolean allowPulseTriggers) {
+ WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager) {
mContext = context;
mMachine = machine;
mDozeHost = dozeHost;
@@ -93,6 +97,7 @@
config, wakeLock, this::onSensor, this::onProximityFar,
dozeParameters.getPolicy());
mUiModeManager = mContext.getSystemService(UiModeManager.class);
+ mDockManager = dockManager;
}
private void onNotification() {
@@ -129,7 +134,8 @@
}
}
- private void onSensor(int pulseReason, boolean sensorPerformedProxCheck,
+ @VisibleForTesting
+ void onSensor(int pulseReason, boolean sensorPerformedProxCheck,
float screenX, float screenY, float[] rawValues) {
boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
boolean isTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_TAP;
@@ -159,7 +165,7 @@
} else {
mDozeHost.extendPulse();
}
- }, sensorPerformedProxCheck, pulseReason);
+ }, sensorPerformedProxCheck || mDockManager.isDocked(), pulseReason);
}
if (isPickup) {
@@ -223,6 +229,7 @@
case INITIALIZED:
mBroadcastReceiver.register(mContext);
mDozeHost.addCallback(mHostCallback);
+ mDockManager.addListener(mDockEventListener);
checkTriggersAtInit();
break;
case DOZE:
@@ -248,6 +255,7 @@
case FINISH:
mBroadcastReceiver.unregister(mContext);
mDozeHost.removeCallback(mHostCallback);
+ mDockManager.removeListener(mDockEventListener);
mDozeSensors.setListening(false);
mDozeSensors.setProxListening(false);
break;
@@ -423,6 +431,24 @@
}
}
+ private class DockEventListener implements DockManager.DockEventListener {
+ @Override
+ public void onEvent(int event) {
+ if (DEBUG) Log.d(TAG, "dock event = " + event);
+ switch (event) {
+ case DockManager.STATE_DOCKED:
+ case DockManager.STATE_DOCKED_HIDE:
+ mDozeSensors.ignoreTouchScreenSensorsSettingInterferingWithDocking(true);
+ break;
+ case DockManager.STATE_NONE:
+ mDozeSensors.ignoreTouchScreenSensorsSettingInterferingWithDocking(false);
+ break;
+ default:
+ // no-op
+ }
+ }
+ }
+
private DozeHost.Callback mHostCallback = new DozeHost.Callback() {
@Override
public void onNotificationAlerted() {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
index 77e25e3..26c6d50 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
@@ -30,6 +30,7 @@
import com.android.systemui.Dependency
import com.android.systemui.R
import com.android.systemui.plugins.ActivityStarter
+import java.util.concurrent.TimeUnit
class OngoingPrivacyDialog constructor(
val context: Context,
@@ -60,7 +61,8 @@
setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null)
setPositiveButton(R.string.ongoing_privacy_dialog_open_settings,
object : DialogInterface.OnClickListener {
- val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE)
+ val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE).putExtra(
+ Intent.EXTRA_DURATION_MILLIS, TimeUnit.MINUTES.toMillis(1))
@Suppress("DEPRECATION")
override fun onClick(dialog: DialogInterface?, which: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 1155a41..e1becdb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -188,7 +188,7 @@
state.secondaryLabel = r.getString(R.string.status_bar_airplane);
} else if (mobileDataEnabled) {
state.state = Tile.STATE_ACTIVE;
- state.secondaryLabel = getMobileDataDescription(cb);
+ state.secondaryLabel = getMobileDataSubscriptionName(cb);
} else {
state.state = Tile.STATE_INACTIVE;
state.secondaryLabel = r.getString(R.string.cell_data_off);
@@ -207,16 +207,16 @@
state.contentDescription = state.label + ", " + contentDescriptionSuffix;
}
- private CharSequence getMobileDataDescription(CallbackInfo cb) {
- if (cb.roaming && !TextUtils.isEmpty(cb.dataContentDescription)) {
+ private CharSequence getMobileDataSubscriptionName(CallbackInfo cb) {
+ if (cb.roaming && !TextUtils.isEmpty(cb.dataSubscriptionName)) {
String roaming = mContext.getString(R.string.data_connection_roaming);
- String dataDescription = cb.dataContentDescription;
+ String dataDescription = cb.dataSubscriptionName.toString();
return mContext.getString(R.string.mobile_data_text_format, roaming, dataDescription);
}
if (cb.roaming) {
return mContext.getString(R.string.data_connection_roaming);
}
- return cb.dataContentDescription;
+ return cb.dataSubscriptionName;
}
@Override
@@ -231,7 +231,7 @@
private static final class CallbackInfo {
boolean airplaneModeEnabled;
- String dataContentDescription;
+ CharSequence dataSubscriptionName;
boolean activityIn;
boolean activityOut;
boolean noSim;
@@ -249,7 +249,7 @@
// Not data sim, don't display.
return;
}
- mInfo.dataContentDescription = typeContentDescription;
+ mInfo.dataSubscriptionName = mController.getMobileDataNetworkName();
mInfo.activityIn = activityIn;
mInfo.activityOut = activityOut;
mInfo.roaming = roaming;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index d5f4d04..d2ce31d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -49,6 +49,7 @@
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -130,8 +131,11 @@
final int count =
getEntryManager().getNotificationData().getActiveNotifications().size();
final int rank = getEntryManager().getNotificationData().getRank(notificationKey);
+ NotificationVisibility.NotificationLocation location =
+ NotificationLogger.getNotificationLocation(
+ getEntryManager().getNotificationData().get(notificationKey));
final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
- rank, count, true);
+ rank, count, true, location);
try {
mBarService.onNotificationClick(notificationKey, nv);
} catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 7d6231f..31d1621 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -54,6 +54,7 @@
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.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -181,7 +182,11 @@
final int rank = mEntryManager.getNotificationData().getRank(key);
final Notification.Action action =
statusBarNotification.getNotification().actions[actionIndex];
- final NotificationVisibility nv = NotificationVisibility.obtain(key, rank, count, true);
+ NotificationVisibility.NotificationLocation location =
+ NotificationLogger.getNotificationLocation(
+ mEntryManager.getNotificationData().get(key));
+ final NotificationVisibility nv =
+ NotificationVisibility.obtain(key, rank, count, true, location);
try {
mBarService.onNotificationActionClick(key, buttonIndex, action, nv, false);
} catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 9ef9c94..546b2e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -538,6 +538,7 @@
- getIntrinsicHeight());
}
float viewEnd = viewStart + fullHeight;
+ // TODO: fix this check for anchor scrolling.
if (expandingAnimated && mAmbientState.getScrollY() == 0
&& !mAmbientState.isOnKeyguard() && !iconState.isLastExpandIcon) {
// We are expanding animated. Because we switch to a linear interpolation in this case,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
index 573c1f8..a2abcd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
@@ -23,6 +23,7 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import java.util.Set;
@@ -74,8 +75,10 @@
boolean generatedByAssistant) {
final int count = mEntryManager.getNotificationData().getActiveNotifications().size();
final int rank = mEntryManager.getNotificationData().getRank(entry.key);
- final NotificationVisibility nv =
- NotificationVisibility.obtain(entry.key, rank, count, true);
+ NotificationVisibility.NotificationLocation location =
+ NotificationLogger.getNotificationLocation(entry);
+ final NotificationVisibility nv = NotificationVisibility.obtain(
+ entry.key, rank, count, true, location);
try {
mBarService.onNotificationActionClick(
entry.key, actionIndex, action, nv, generatedByAssistant);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 989e781..ef5e936 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -36,6 +36,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationInflater;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -160,8 +161,10 @@
public void performRemoveNotification(StatusBarNotification n) {
final int rank = mNotificationData.getRank(n.getKey());
final int count = mNotificationData.getActiveNotifications().size();
+ NotificationVisibility.NotificationLocation location =
+ NotificationLogger.getNotificationLocation(getNotificationData().get(n.getKey()));
final NotificationVisibility nv = NotificationVisibility.obtain(n.getKey(), rank, count,
- true);
+ true, location);
removeNotificationInternal(
n.getKey(), null, nv, false /* forceRemove */, true /* removedByUser */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 5ba9b4b..7b94c74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -39,6 +39,8 @@
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.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -127,7 +129,8 @@
NotificationEntry entry = activeNotifications.get(i);
String key = entry.notification.getKey();
boolean isVisible = mListContainer.isInVisibleLocation(entry);
- NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible);
+ NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible,
+ getNotificationLocation(entry));
boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
if (isVisible) {
// Build new set of visible notifications.
@@ -159,6 +162,40 @@
}
};
+ /**
+ * Returns the location of the notification referenced by the given {@link NotificationEntry}.
+ */
+ public static NotificationVisibility.NotificationLocation getNotificationLocation(
+ NotificationEntry entry) {
+ ExpandableNotificationRow row = entry.getRow();
+ ExpandableViewState childViewState = row.getViewState();
+
+ if (childViewState == null) {
+ return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
+ }
+ return convertNotificationLocation(childViewState.location);
+ }
+
+ private static NotificationVisibility.NotificationLocation convertNotificationLocation(
+ int location) {
+ switch (location) {
+ case ExpandableViewState.LOCATION_FIRST_HUN:
+ return NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP;
+ case ExpandableViewState.LOCATION_HIDDEN_TOP:
+ return NotificationVisibility.NotificationLocation.LOCATION_HIDDEN_TOP;
+ case ExpandableViewState.LOCATION_MAIN_AREA:
+ return NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA;
+ case ExpandableViewState.LOCATION_BOTTOM_STACK_PEEKING:
+ return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_PEEKING;
+ case ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN:
+ return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_HIDDEN;
+ case ExpandableViewState.LOCATION_GONE:
+ return NotificationVisibility.NotificationLocation.LOCATION_GONE;
+ default:
+ return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
+ }
+ }
+
@Inject
public NotificationLogger(NotificationListener notificationListener,
UiOffloadThread uiOffloadThread,
@@ -362,7 +399,9 @@
* Called when the notification is expanded / collapsed.
*/
public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
- mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded);
+ NotificationVisibility.NotificationLocation location =
+ getNotificationLocation(mEntryManager.getNotificationData().get(key));
+ mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded, location);
}
/**
@@ -396,10 +435,12 @@
}
@VisibleForTesting
- void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
+ void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded,
+ NotificationVisibility.NotificationLocation location) {
State state = getState(key);
state.mIsUserAction = isUserAction;
state.mIsExpanded = isExpanded;
+ state.mLocation = location;
maybeNotifyOnNotificationExpansionChanged(key, state);
}
@@ -415,6 +456,7 @@
for (NotificationVisibility nv : newlyVisibleAr) {
State state = getState(nv.key);
state.mIsVisible = true;
+ state.mLocation = nv.location;
maybeNotifyOnNotificationExpansionChanged(nv.key, state);
}
for (NotificationVisibility nv : noLongerVisibleAr) {
@@ -459,8 +501,8 @@
final State stateToBeLogged = new State(state);
mUiOffloadThread.submit(() -> {
try {
- mBarService.onNotificationExpansionChanged(
- key, stateToBeLogged.mIsUserAction, stateToBeLogged.mIsExpanded);
+ mBarService.onNotificationExpansionChanged(key, stateToBeLogged.mIsUserAction,
+ stateToBeLogged.mIsExpanded, stateToBeLogged.mLocation.ordinal());
} catch (RemoteException e) {
Log.e(TAG, "Failed to call onNotificationExpansionChanged: ", e);
}
@@ -474,6 +516,8 @@
Boolean mIsExpanded;
@Nullable
Boolean mIsVisible;
+ @Nullable
+ NotificationVisibility.NotificationLocation mLocation;
private State() {}
@@ -481,10 +525,12 @@
this.mIsUserAction = state.mIsUserAction;
this.mIsExpanded = state.mIsExpanded;
this.mIsVisible = state.mIsVisible;
+ this.mLocation = state.mLocation;
}
private boolean isFullySet() {
- return mIsUserAction != null && mIsExpanded != null && mIsVisible != null;
+ return mIsUserAction != null && mIsExpanded != null && mIsVisible != null
+ && mLocation != null;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index bd1dfb1..5e9f207 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.metrics.LogMaker;
import android.net.Uri;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -159,7 +160,8 @@
return bindGuts(row, mGutsMenuItem);
}
- private boolean bindGuts(final ExpandableNotificationRow row,
+ @VisibleForTesting
+ protected boolean bindGuts(final ExpandableNotificationRow row,
NotificationMenuRowPlugin.MenuItem item) {
StatusBarNotification sbn = row.getStatusBarNotification();
@@ -389,7 +391,11 @@
return false;
}
- mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS);
+ LogMaker logMaker = (row.getStatusBarNotification() == null)
+ ? new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS)
+ : row.getStatusBarNotification().getLogMaker();
+ mMetricsLogger.write(logMaker.setCategory(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS)
+ .setType(MetricsProto.MetricsEvent.TYPE_ACTION));
// ensure that it's laid but not visible until actually laid out
guts.setVisibility(View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 1b5013d..c246af5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -42,6 +42,8 @@
private ArrayList<ExpandableView> mDraggedViews = new ArrayList<>();
private int mScrollY;
+ private int mAnchorViewIndex;
+ private int mAnchorViewY;
private boolean mDimmed;
private ActivatableNotificationView mActivatedChild;
private float mOverScrollTopAmount;
@@ -130,6 +132,27 @@
this.mScrollY = scrollY;
}
+ /**
+ * Index of the child view whose Y position on screen is returned by {@link #getAnchorViewY()}.
+ * Other views are laid out outwards from this view in both directions.
+ */
+ public int getAnchorViewIndex() {
+ return mAnchorViewIndex;
+ }
+
+ public void setAnchorViewIndex(int anchorViewIndex) {
+ mAnchorViewIndex = anchorViewIndex;
+ }
+
+ /** Current Y position of the view at {@link #getAnchorViewIndex()}. */
+ public int getAnchorViewY() {
+ return mAnchorViewY;
+ }
+
+ public void setAnchorViewY(int anchorViewY) {
+ mAnchorViewY = anchorViewY;
+ }
+
/** Call when dragging begins. */
public void onBeginDrag(ExpandableView view) {
mDraggedViews.add(view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index d066567..2129b81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -18,6 +18,7 @@
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
import static com.android.systemui.statusbar.phone.NotificationIconAreaController.LOW_PRIORITY;
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
@@ -176,10 +177,18 @@
private float mExpandedHeight;
private int mOwnScrollY;
+ private View mScrollAnchorView;
+ private int mScrollAnchorViewY;
private int mMaxLayoutHeight;
private VelocityTracker mVelocityTracker;
private OverScroller mScroller;
+ /** Last Y position reported by {@link #mScroller}, used to calculate scroll delta. */
+ private int mLastScrollerY;
+ /**
+ * True if the max position was set to a known position on the last call to {@link #mScroller}.
+ */
+ private boolean mIsScrollerBoundSet;
private Runnable mFinishScrollingCallback;
private int mTouchSlop;
private int mMinimumVelocity;
@@ -417,7 +426,12 @@
private int mStatusBarState;
private int mCachedBackgroundColor;
private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
- private Runnable mAnimateScroll = this::animateScroll;
+ private Runnable mReflingAndAnimateScroll = () -> {
+ if (ANCHOR_SCROLLING) {
+ maybeReflingScroller();
+ }
+ animateScroll();
+ };
private int mCornerRadius;
private int mSidePaddings;
private final Rect mBackgroundAnimationRect = new Rect();
@@ -511,6 +525,7 @@
mDebugPaint.setColor(0xffff0000);
mDebugPaint.setStrokeWidth(2);
mDebugPaint.setStyle(Paint.Style.STROKE);
+ mDebugPaint.setTextSize(25f);
}
mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
@@ -674,6 +689,30 @@
}
}
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ if (DEBUG && ANCHOR_SCROLLING) {
+ if (mScrollAnchorView instanceof ExpandableNotificationRow) {
+ canvas.drawRect(0,
+ mScrollAnchorView.getTranslationY(),
+ getWidth(),
+ mScrollAnchorView.getTranslationY()
+ + ((ExpandableNotificationRow) mScrollAnchorView).getActualHeight(),
+ mDebugPaint);
+ canvas.drawText(Integer.toString(mScrollAnchorViewY), getWidth() - 200,
+ mScrollAnchorView.getTranslationY() + 30, mDebugPaint);
+ int y = (int) mShelf.getTranslationY();
+ canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+ }
+ canvas.drawText(Integer.toString(getMaxNegativeScrollAmount()), getWidth() - 100,
+ getIntrinsicPadding() + 30, mDebugPaint);
+ canvas.drawText(Integer.toString(getMaxPositiveScrollAmount()), getWidth() - 100,
+ getHeight() - 30, mDebugPaint);
+ }
+ }
+
@ShadeViewRefactor(RefactorComponent.DECORATOR)
private void drawBackground(Canvas canvas) {
int lockScreenLeft = mSidePaddings;
@@ -970,7 +1009,12 @@
mAmbientState.setCurrentScrollVelocity(mScroller.isFinished()
? 0
: mScroller.getCurrVelocity());
- mAmbientState.setScrollY(mOwnScrollY);
+ if (ANCHOR_SCROLLING) {
+ mAmbientState.setAnchorViewIndex(indexOfChild(mScrollAnchorView));
+ mAmbientState.setAnchorViewY(mScrollAnchorViewY);
+ } else {
+ mAmbientState.setScrollY(mOwnScrollY);
+ }
mStackScrollAlgorithm.resetViewStates(mAmbientState);
if (!isCurrentlyAnimating() && !mNeedsAnimation) {
applyCurrentState();
@@ -1004,7 +1048,7 @@
float end = start + child.getActualHeight();
boolean clip = clipStart > start && clipStart < end
|| clipEnd >= start && clipEnd <= end;
- clip &= !(first && mOwnScrollY == 0);
+ clip &= !(first && isScrolledToTop());
child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0)
: ExpandableView.NO_ROUNDNESS);
first = false;
@@ -1016,19 +1060,21 @@
if (mChildrenToAddAnimated.isEmpty()) {
return;
}
- for (int i = 0; i < getChildCount(); i++) {
- ExpandableView child = (ExpandableView) getChildAt(i);
- if (mChildrenToAddAnimated.contains(child)) {
- int startingPosition = getPositionInLinearLayout(child);
- float increasedPaddingAmount = child.getIncreasedPaddingAmount();
- int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements
- : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements;
- int childHeight = getIntrinsicHeight(child) + padding;
- if (startingPosition < mOwnScrollY) {
- // This child starts off screen, so let's keep it offscreen to keep the
- // others visible
+ if (!ANCHOR_SCROLLING) {
+ for (int i = 0; i < getChildCount(); i++) {
+ ExpandableView child = (ExpandableView) getChildAt(i);
+ if (mChildrenToAddAnimated.contains(child)) {
+ int startingPosition = getPositionInLinearLayout(child);
+ float increasedPaddingAmount = child.getIncreasedPaddingAmount();
+ int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements
+ : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements;
+ int childHeight = getIntrinsicHeight(child) + padding;
+ if (startingPosition < mOwnScrollY) {
+ // This child starts off screen, so let's keep it offscreen to keep the
+ // others visible
- setOwnScrollY(mOwnScrollY + childHeight);
+ setOwnScrollY(mOwnScrollY + childHeight);
+ }
}
}
}
@@ -1047,12 +1093,16 @@
int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
- targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
- // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
- // that it is not visible anymore.
- if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
- setOwnScrollY(targetScroll);
+ // Only apply the scroll if we're scrolling the view upwards, or the view is so
+ // far up that it is not visible anymore.
+ if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
+ setOwnScrollY(targetScroll);
+ }
}
}
}
@@ -1073,9 +1123,13 @@
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private void clampScrollPosition() {
- int scrollRange = getScrollRange();
- if (scrollRange < mOwnScrollY) {
- setOwnScrollY(scrollRange);
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ int scrollRange = getScrollRange();
+ if (scrollRange < mOwnScrollY) {
+ setOwnScrollY(scrollRange);
+ }
}
}
@@ -1453,17 +1507,21 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public boolean scrollTo(View v) {
ExpandableView expandableView = (ExpandableView) v;
- int positionInLinearLayout = getPositionInLinearLayout(v);
- int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
- int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ int positionInLinearLayout = getPositionInLinearLayout(v);
+ int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
+ int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
- // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
- // that it is not visible anymore.
- if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
- mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
- mDontReportNextOverScroll = true;
- animateScroll();
- return true;
+ // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
+ // that it is not visible anymore.
+ if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
+ mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
+ mDontReportNextOverScroll = true;
+ animateScroll();
+ return true;
+ }
}
return false;
}
@@ -1484,16 +1542,20 @@
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
mBottomInset = insets.getSystemWindowInsetBottom();
- int range = getScrollRange();
- if (mOwnScrollY > range) {
- // HACK: We're repeatedly getting staggered insets here while the IME is
- // animating away. To work around that we'll wait until things have settled.
- removeCallbacks(mReclamp);
- postDelayed(mReclamp, 50);
- } else if (mForcedScroll != null) {
- // The scroll was requested before we got the actual inset - in case we need
- // to scroll up some more do so now.
- scrollTo(mForcedScroll);
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ int range = getScrollRange();
+ if (mOwnScrollY > range) {
+ // HACK: We're repeatedly getting staggered insets here while the IME is
+ // animating away. To work around that we'll wait until things have settled.
+ removeCallbacks(mReclamp);
+ postDelayed(mReclamp, 50);
+ } else if (mForcedScroll != null) {
+ // The scroll was requested before we got the actual inset - in case we need
+ // to scroll up some more do so now.
+ scrollTo(mForcedScroll);
+ }
}
return insets;
}
@@ -1502,8 +1564,12 @@
private Runnable mReclamp = new Runnable() {
@Override
public void run() {
- int range = getScrollRange();
- mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ int range = getScrollRange();
+ mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
+ }
mDontReportNextOverScroll = true;
mDontClampNextScroll = true;
animateScroll();
@@ -1581,20 +1647,39 @@
}
// Top overScroll might not grab all scrolling motion,
// we have to scroll as well.
- float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
- float newScrollY = mOwnScrollY + scrollAmount;
- if (newScrollY > range) {
- if (!mExpandedInThisMotion) {
- float currentBottomPixels = getCurrentOverScrolledPixels(false);
- // We overScroll on the top
- setOverScrolledPixels(currentBottomPixels + newScrollY - range,
- false /* onTop */,
- false /* animate */);
+ if (ANCHOR_SCROLLING) {
+ float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
+ // TODO: once we're recycling this will need to check the adapter position of the child
+ ExpandableView lastRow = getLastRowNotGone();
+ if (lastRow != null && !lastRow.isInShelf()) {
+ float distanceToMax = Math.max(0, getMaxPositiveScrollAmount());
+ if (scrollAmount > distanceToMax) {
+ float currentBottomPixels = getCurrentOverScrolledPixels(false);
+ // We overScroll on the bottom
+ setOverScrolledPixels(currentBottomPixels + (scrollAmount - distanceToMax),
+ false /* onTop */,
+ false /* animate */);
+ mScrollAnchorViewY -= distanceToMax;
+ scrollAmount = 0f;
+ }
}
- setOwnScrollY(range);
- scrollAmount = 0.0f;
+ return scrollAmount;
+ } else {
+ float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
+ float newScrollY = mOwnScrollY + scrollAmount;
+ if (newScrollY > range) {
+ if (!mExpandedInThisMotion) {
+ float currentBottomPixels = getCurrentOverScrolledPixels(false);
+ // We overScroll on the bottom
+ setOverScrolledPixels(currentBottomPixels + newScrollY - range,
+ false /* onTop */,
+ false /* animate */);
+ }
+ setOwnScrollY(range);
+ scrollAmount = 0.0f;
+ }
+ return scrollAmount;
}
- return scrollAmount;
}
/**
@@ -1615,18 +1700,37 @@
}
// Bottom overScroll might not grab all scrolling motion,
// we have to scroll as well.
- float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
- float newScrollY = mOwnScrollY + scrollAmount;
- if (newScrollY < 0) {
- float currentTopPixels = getCurrentOverScrolledPixels(true);
- // We overScroll on the top
- setOverScrolledPixels(currentTopPixels - newScrollY,
- true /* onTop */,
- false /* animate */);
- setOwnScrollY(0);
- scrollAmount = 0.0f;
+ if (ANCHOR_SCROLLING) {
+ float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
+ // TODO: once we're recycling this will need to check the adapter position of the child
+ ExpandableView firstChild = getFirstChildNotGone();
+ float top = firstChild.getTranslationY();
+ float distanceToTop = mScrollAnchorView.getTranslationY() - top - mScrollAnchorViewY;
+ if (distanceToTop < -scrollAmount) {
+ float currentTopPixels = getCurrentOverScrolledPixels(true);
+ // We overScroll on the top
+ setOverScrolledPixels(currentTopPixels + (-scrollAmount - distanceToTop),
+ true /* onTop */,
+ false /* animate */);
+ mScrollAnchorView = firstChild;
+ mScrollAnchorViewY = 0;
+ scrollAmount = 0f;
+ }
+ return scrollAmount;
+ } else {
+ float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
+ float newScrollY = mOwnScrollY + scrollAmount;
+ if (newScrollY < 0) {
+ float currentTopPixels = getCurrentOverScrolledPixels(true);
+ // We overScroll on the top
+ setOverScrolledPixels(currentTopPixels - newScrollY,
+ true /* onTop */,
+ false /* animate */);
+ setOwnScrollY(0);
+ scrollAmount = 0.0f;
+ }
+ return scrollAmount;
}
- return scrollAmount;
}
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -1661,26 +1765,43 @@
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
private void animateScroll() {
if (mScroller.computeScrollOffset()) {
- int oldY = mOwnScrollY;
- int y = mScroller.getCurrY();
-
- if (oldY != y) {
- int range = getScrollRange();
- if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
- float currVelocity = mScroller.getCurrVelocity();
- if (currVelocity >= mMinimumVelocity) {
- mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
+ if (ANCHOR_SCROLLING) {
+ int oldY = mLastScrollerY;
+ int y = mScroller.getCurrY();
+ int deltaY = y - oldY;
+ if (deltaY != 0) {
+ int maxNegativeScrollAmount = getMaxNegativeScrollAmount();
+ int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
+ if ((maxNegativeScrollAmount < 0 && deltaY < maxNegativeScrollAmount)
+ || (maxPositiveScrollAmount > 0 && deltaY > maxPositiveScrollAmount)) {
+ // This frame takes us into overscroll, so set the max overscroll based on
+ // the current velocity
+ setMaxOverScrollFromCurrentVelocity();
}
+ customOverScrollBy(deltaY, oldY, 0, (int) mMaxOverScroll);
+ mLastScrollerY = y;
}
+ } else {
+ int oldY = mOwnScrollY;
+ int y = mScroller.getCurrY();
- if (mDontClampNextScroll) {
- range = Math.max(range, oldY);
+ if (oldY != y) {
+ int range = getScrollRange();
+ if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
+ // This frame takes us into overscroll, so set the max overscroll based on
+ // the current velocity
+ setMaxOverScrollFromCurrentVelocity();
+ }
+
+ if (mDontClampNextScroll) {
+ range = Math.max(range, oldY);
+ }
+ customOverScrollBy(y - oldY, oldY, range,
+ (int) (mMaxOverScroll));
}
- customOverScrollBy(y - oldY, oldY, range,
- (int) (mMaxOverScroll));
}
- postOnAnimation(mAnimateScroll);
+ postOnAnimation(mReflingAndAnimateScroll);
} else {
mDontClampNextScroll = false;
if (mFinishScrollingCallback != null) {
@@ -1689,26 +1810,67 @@
}
}
- @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
- private boolean customOverScrollBy(int deltaY, int scrollY, int scrollRangeY,
- int maxOverScrollY) {
-
- int newScrollY = scrollY + deltaY;
- final int top = -maxOverScrollY;
- final int bottom = maxOverScrollY + scrollRangeY;
-
- boolean clampedY = false;
- if (newScrollY > bottom) {
- newScrollY = bottom;
- clampedY = true;
- } else if (newScrollY < top) {
- newScrollY = top;
- clampedY = true;
+ private void setMaxOverScrollFromCurrentVelocity() {
+ float currVelocity = mScroller.getCurrVelocity();
+ if (currVelocity >= mMinimumVelocity) {
+ mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
}
+ }
- onCustomOverScrolled(newScrollY, clampedY);
+ /**
+ * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta
+ * would cause us to exceed the provided maximum overscroll, springs back instead.
+ *
+ * This method performs the determination of whether we're exceeding the overscroll and clamps
+ * the scroll amount if so. The actual scrolling/overscrolling happens in
+ * {@link #onCustomOverScrolled(int, boolean)} (absolute scrolling) or
+ * {@link #onCustomOverScrolledBy(int, boolean)} (anchor scrolling).
+ *
+ * @param deltaY The (signed) number of pixels to scroll.
+ * @param scrollY The current scroll position (absolute scrolling only).
+ * @param scrollRangeY The maximum allowable scroll position (absolute scrolling only).
+ * @param maxOverScrollY The current (unsigned) limit on number of pixels to overscroll by.
+ */
+ @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
+ private void customOverScrollBy(int deltaY, int scrollY, int scrollRangeY, int maxOverScrollY) {
+ if (ANCHOR_SCROLLING) {
+ boolean clampedY = false;
+ if (deltaY < 0) {
+ int maxScrollAmount = getMaxNegativeScrollAmount();
+ if (maxScrollAmount > Integer.MIN_VALUE) {
+ maxScrollAmount -= maxOverScrollY;
+ if (deltaY < maxScrollAmount) {
+ deltaY = maxScrollAmount;
+ clampedY = true;
+ }
+ }
+ } else {
+ int maxScrollAmount = getMaxPositiveScrollAmount();
+ if (maxScrollAmount < Integer.MAX_VALUE) {
+ maxScrollAmount += maxOverScrollY;
+ if (deltaY > maxScrollAmount) {
+ deltaY = maxScrollAmount;
+ clampedY = true;
+ }
+ }
+ }
+ onCustomOverScrolledBy(deltaY, clampedY);
+ } else {
+ int newScrollY = scrollY + deltaY;
+ final int top = -maxOverScrollY;
+ final int bottom = maxOverScrollY + scrollRangeY;
- return clampedY;
+ boolean clampedY = false;
+ if (newScrollY > bottom) {
+ newScrollY = bottom;
+ clampedY = true;
+ } else if (newScrollY < top) {
+ newScrollY = top;
+ clampedY = true;
+ }
+
+ onCustomOverScrolled(newScrollY, clampedY);
+ }
}
/**
@@ -1826,8 +1988,46 @@
}
}
+ /**
+ * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta
+ * would cause us to exceed the provided maximum overscroll, springs back instead.
+ *
+ * @param deltaY The (signed) number of pixels to scroll.
+ * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
+ * the overscroll limit.
+ */
+ private void onCustomOverScrolledBy(int deltaY, boolean clampedY) {
+ assert ANCHOR_SCROLLING;
+ mScrollAnchorViewY -= deltaY;
+ // Treat animating scrolls differently; see #computeScroll() for why.
+ if (!mScroller.isFinished()) {
+ if (clampedY) {
+ springBack();
+ } else {
+ float overScrollTop = getCurrentOverScrollAmount(true /* top */);
+ if (isScrolledToTop() && mScrollAnchorViewY > 0) {
+ notifyOverscrollTopListener(mScrollAnchorViewY,
+ isRubberbanded(true /* onTop */));
+ } else {
+ notifyOverscrollTopListener(overScrollTop, isRubberbanded(true /* onTop */));
+ }
+ }
+ }
+ updateScrollAnchor();
+ updateOnScrollChange();
+ }
+
+ /**
+ * Scrolls to the given position, overscrolling if needed. If called during a fling and the
+ * position exceeds the provided maximum overscroll, springs back instead.
+ *
+ * @param scrollY The target scroll position.
+ * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
+ * the overscroll limit.
+ */
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private void onCustomOverScrolled(int scrollY, boolean clampedY) {
+ assert !ANCHOR_SCROLLING;
// Treat animating scrolls differently; see #computeScroll() for why.
if (!mScroller.isFinished()) {
setOwnScrollY(scrollY);
@@ -1846,27 +2046,51 @@
}
}
+ /**
+ * Springs back from an overscroll by stopping the {@link #mScroller} and animating the
+ * overscroll amount back to zero.
+ */
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
private void springBack() {
- int scrollRange = getScrollRange();
- boolean overScrolledTop = mOwnScrollY <= 0;
- boolean overScrolledBottom = mOwnScrollY >= scrollRange;
- if (overScrolledTop || overScrolledBottom) {
- boolean onTop;
- float newAmount;
- if (overScrolledTop) {
- onTop = true;
- newAmount = -mOwnScrollY;
- setOwnScrollY(0);
- mDontReportNextOverScroll = true;
- } else {
- onTop = false;
- newAmount = mOwnScrollY - scrollRange;
- setOwnScrollY(scrollRange);
+ if (ANCHOR_SCROLLING) {
+ boolean overScrolledTop = isScrolledToTop() && mScrollAnchorViewY > 0;
+ int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
+ boolean overscrolledBottom = maxPositiveScrollAmount < 0;
+ if (overScrolledTop || overscrolledBottom) {
+ float newAmount;
+ if (overScrolledTop) {
+ newAmount = mScrollAnchorViewY;
+ mScrollAnchorViewY = 0;
+ mDontReportNextOverScroll = true;
+ } else {
+ newAmount = -maxPositiveScrollAmount;
+ mScrollAnchorViewY -= maxPositiveScrollAmount;
+ }
+ setOverScrollAmount(newAmount, overScrolledTop, false);
+ setOverScrollAmount(0.0f, overScrolledTop, true);
+ mScroller.forceFinished(true);
}
- setOverScrollAmount(newAmount, onTop, false);
- setOverScrollAmount(0.0f, onTop, true);
- mScroller.forceFinished(true);
+ } else {
+ int scrollRange = getScrollRange();
+ boolean overScrolledTop = mOwnScrollY <= 0;
+ boolean overScrolledBottom = mOwnScrollY >= scrollRange;
+ if (overScrolledTop || overScrolledBottom) {
+ boolean onTop;
+ float newAmount;
+ if (overScrolledTop) {
+ onTop = true;
+ newAmount = -mOwnScrollY;
+ setOwnScrollY(0);
+ mDontReportNextOverScroll = true;
+ } else {
+ onTop = false;
+ newAmount = mOwnScrollY - scrollRange;
+ setOwnScrollY(scrollRange);
+ }
+ setOverScrollAmount(newAmount, onTop, false);
+ setOverScrollAmount(0.0f, onTop, true);
+ mScroller.forceFinished(true);
+ }
}
}
@@ -1971,6 +2195,17 @@
return null;
}
+ private ExpandableNotificationRow getLastRowNotGone() {
+ int childCount = getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ View child = getChildAt(i);
+ if (child instanceof ExpandableNotificationRow && child.getVisibility() != View.GONE) {
+ return (ExpandableNotificationRow) child;
+ }
+ }
+ return null;
+ }
+
/**
* @return the number of children which have visibility unequal to GONE
*/
@@ -2081,8 +2316,8 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
private void updateForwardAndBackwardScrollability() {
- boolean forwardScrollable = mScrollable && mOwnScrollY < getScrollRange();
- boolean backwardsScrollable = mScrollable && mOwnScrollY > 0;
+ boolean forwardScrollable = mScrollable && !isScrolledToBottom();
+ boolean backwardsScrollable = mScrollable && !isScrolledToTop();
boolean changed = forwardScrollable != mForwardScrollable
|| backwardsScrollable != mBackwardScrollable;
mForwardScrollable = forwardScrollable;
@@ -2403,18 +2638,24 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
protected void fling(int velocityY) {
if (getChildCount() > 0) {
- int scrollRange = getScrollRange();
-
float topAmount = getCurrentOverScrollAmount(true);
float bottomAmount = getCurrentOverScrollAmount(false);
if (velocityY < 0 && topAmount > 0) {
- setOwnScrollY(mOwnScrollY - (int) topAmount);
+ if (ANCHOR_SCROLLING) {
+ mScrollAnchorViewY += topAmount;
+ } else {
+ setOwnScrollY(mOwnScrollY - (int) topAmount);
+ }
mDontReportNextOverScroll = true;
setOverScrollAmount(0, true, false);
mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
* mOverflingDistance + topAmount;
} else if (velocityY > 0 && bottomAmount > 0) {
- setOwnScrollY((int) (mOwnScrollY + bottomAmount));
+ if (ANCHOR_SCROLLING) {
+ mScrollAnchorViewY -= bottomAmount;
+ } else {
+ setOwnScrollY((int) (mOwnScrollY + bottomAmount));
+ }
setOverScrollAmount(0, false, false);
mMaxOverScroll = Math.abs(velocityY) / 1000f
* getRubberBandFactor(false /* onTop */) * mOverflingDistance
@@ -2423,18 +2664,138 @@
// it will be set once we reach the boundary
mMaxOverScroll = 0.0f;
}
- int minScrollY = Math.max(0, scrollRange);
- if (mExpandedInThisMotion) {
- minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
+ if (ANCHOR_SCROLLING) {
+ flingScroller(velocityY);
+ } else {
+ int scrollRange = getScrollRange();
+ int minScrollY = Math.max(0, scrollRange);
+ if (mExpandedInThisMotion) {
+ minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
+ }
+ mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0,
+ mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
}
- mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0,
- mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
animateScroll();
}
}
/**
+ * Flings the overscroller with the given velocity (anchor-based scrolling).
+ *
+ * Because anchor-based scrolling can't track the current scroll position, the overscroller is
+ * always started at startY = 0, and we interpret the positions it computes as relative to the
+ * start of the scroll.
+ */
+ private void flingScroller(int velocityY) {
+ assert ANCHOR_SCROLLING;
+ mIsScrollerBoundSet = false;
+ maybeFlingScroller(velocityY, true /* always fling */);
+ }
+
+ private void maybeFlingScroller(int velocityY, boolean alwaysFling) {
+ assert ANCHOR_SCROLLING;
+ // Attempt to determine the maximum amount to scroll before we reach the end.
+ // If the first view is not materialized (for an upwards scroll) or the last view is either
+ // not materialized or is pinned to the shade (for a downwards scroll), we don't know this
+ // amount, so we do an unbounded fling and rely on {@link #maybeReflingScroller()} to update
+ // the scroller once we approach the start/end of the list.
+ int minY = Integer.MIN_VALUE;
+ int maxY = Integer.MAX_VALUE;
+ if (velocityY < 0) {
+ minY = getMaxNegativeScrollAmount();
+ if (minY > Integer.MIN_VALUE) {
+ mIsScrollerBoundSet = true;
+ }
+ } else {
+ maxY = getMaxPositiveScrollAmount();
+ if (maxY < Integer.MAX_VALUE) {
+ mIsScrollerBoundSet = true;
+ }
+ }
+ if (mIsScrollerBoundSet || alwaysFling) {
+ mLastScrollerY = 0;
+ // x velocity is set to 1 to avoid overscroller bug
+ mScroller.fling(0, 0, 1, velocityY, 0, 0, minY, maxY, 0,
+ mExpandedInThisMotion && !isScrolledToTop() ? 0 : Integer.MAX_VALUE / 2);
+ }
+ }
+
+ /**
+ * Returns the maximum number of pixels we can scroll in the positive direction (downwards)
+ * before reaching the bottom of the list (discounting overscroll).
+ *
+ * If the return value is negative then we have overscrolled; this is a transient state which
+ * should immediately be handled by adjusting the anchor position and adding the extra space to
+ * the bottom overscroll amount.
+ *
+ * If we don't know how many pixels we have left to scroll (because the last row has not been
+ * materialized, or it's in the shelf so it doesn't have its "natural" position), we return
+ * {@link Integer#MAX_VALUE}.
+ */
+ private int getMaxPositiveScrollAmount() {
+ assert ANCHOR_SCROLLING;
+ // TODO: once we're recycling we need to check the adapter position of the last child.
+ ExpandableNotificationRow lastRow = getLastRowNotGone();
+ if (mScrollAnchorView != null && lastRow != null && !lastRow.isInShelf()) {
+ // distance from bottom of last child to bottom of notifications area is:
+ // distance from bottom of last child
+ return (int) (lastRow.getTranslationY() + lastRow.getActualHeight()
+ // to top of anchor view
+ - mScrollAnchorView.getTranslationY()
+ // plus distance from anchor view to top of notifications area
+ + mScrollAnchorViewY
+ // minus height of notifications area.
+ - (mMaxLayoutHeight - getIntrinsicPadding() - mFooterView.getActualHeight()));
+ } else {
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ /**
+ * Returns the maximum number of pixels (as a negative number) we can scroll in the negative
+ * direction (upwards) before reaching the top of the list (discounting overscroll).
+ *
+ * If the return value is positive then we have overscrolled; this is a transient state which
+ * should immediately be handled by adjusting the anchor position and adding the extra space to
+ * the top overscroll amount.
+ *
+ * If we don't know how many pixels we have left to scroll (because the first row has not been
+ * materialized), we return {@link Integer#MIN_VALUE}.
+ */
+ private int getMaxNegativeScrollAmount() {
+ assert ANCHOR_SCROLLING;
+ // TODO: once we're recycling we need to check the adapter position of the first child.
+ ExpandableView firstChild = getFirstChildNotGone();
+ if (mScrollAnchorView != null && firstChild != null) {
+ // distance from top of first child to top of notifications area is:
+ // distance from top of anchor view
+ return (int) -(mScrollAnchorView.getTranslationY()
+ // to top of first child
+ - firstChild.getTranslationY()
+ // minus distance from top of anchor view to top of notifications area.
+ - mScrollAnchorViewY);
+ } else {
+ return Integer.MIN_VALUE;
+ }
+ }
+
+ /**
+ * During a fling, if we were unable to set the bounds of the fling due to the top/bottom view
+ * not being materialized or being pinned to the shelf, we need to check on every frame if we're
+ * able to set the bounds. If we are, we fling the scroller again with the newly computed
+ * bounds.
+ */
+ private void maybeReflingScroller() {
+ if (!mIsScrollerBoundSet) {
+ // Because mScroller is a flywheel scroller, we fling with the minimum possible
+ // velocity to establish direction, so as not to perceptibly affect the velocity.
+ maybeFlingScroller((int) Math.signum(mScroller.getCurrVelocity()),
+ false /* alwaysFling */);
+ }
+ }
+
+ /**
* @return Whether a fling performed on the top overscroll edge lead to the expanded
* overScroll view (i.e QS).
*/
@@ -2485,25 +2846,10 @@
}
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
- public int getFirstChildIntrinsicHeight() {
- final ExpandableView firstChild = getFirstChildNotGone();
- int firstChildMinHeight = firstChild != null
- ? firstChild.getIntrinsicHeight()
- : mEmptyShadeView != null
- ? mEmptyShadeView.getIntrinsicHeight()
- : mCollapsedSize;
- if (mOwnScrollY > 0) {
- firstChildMinHeight = Math.max(firstChildMinHeight - mOwnScrollY, mCollapsedSize);
- }
- return firstChildMinHeight;
- }
-
- @ShadeViewRefactor(RefactorComponent.COORDINATOR)
public float getTopPaddingOverflow() {
return mTopPaddingOverflow;
}
-
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public int getPeekHeight() {
final ExpandableView firstChild = getFirstChildNotGone();
@@ -2711,30 +3057,51 @@
*/
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
- int startingPosition = getPositionInLinearLayout(removedChild);
- float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount();
- int padding;
- if (increasedPaddingAmount >= 0) {
- padding = (int) NotificationUtils.interpolate(
- mPaddingBetweenElements,
- mIncreasedPaddingBetweenElements,
- increasedPaddingAmount);
+ if (ANCHOR_SCROLLING) {
+ if (removedChild == mScrollAnchorView) {
+ ExpandableView firstChild = getFirstChildNotGone();
+ if (firstChild != null) {
+ mScrollAnchorView = firstChild;
+ } else {
+ mScrollAnchorView = mShelf;
+ }
+ // Adjust anchor view Y by the distance between the old and new anchors
+ // so that there's no visible change.
+ mScrollAnchorViewY +=
+ mScrollAnchorView.getTranslationY() - removedChild.getTranslationY();
+ }
+ updateScrollAnchor();
+ // TODO: once we're recycling this will need to check the adapter position of the child
+ if (mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY > 0) {
+ mScrollAnchorViewY = 0;
+ }
+ updateOnScrollChange();
} else {
- padding = (int) NotificationUtils.interpolate(
- 0,
- mPaddingBetweenElements,
- 1.0f + increasedPaddingAmount);
- }
- int childHeight = getIntrinsicHeight(removedChild) + padding;
- int endPosition = startingPosition + childHeight;
- if (endPosition <= mOwnScrollY) {
- // This child is fully scrolled of the top, so we have to deduct its height from the
- // scrollPosition
- setOwnScrollY(mOwnScrollY - childHeight);
- } else if (startingPosition < mOwnScrollY) {
- // This child is currently being scrolled into, set the scroll position to the start of
- // this child
- setOwnScrollY(startingPosition);
+ int startingPosition = getPositionInLinearLayout(removedChild);
+ float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount();
+ int padding;
+ if (increasedPaddingAmount >= 0) {
+ padding = (int) NotificationUtils.interpolate(
+ mPaddingBetweenElements,
+ mIncreasedPaddingBetweenElements,
+ increasedPaddingAmount);
+ } else {
+ padding = (int) NotificationUtils.interpolate(
+ 0,
+ mPaddingBetweenElements,
+ 1.0f + increasedPaddingAmount);
+ }
+ int childHeight = getIntrinsicHeight(removedChild) + padding;
+ int endPosition = startingPosition + childHeight;
+ if (endPosition <= mOwnScrollY) {
+ // This child is fully scrolled of the top, so we have to deduct its height from the
+ // scrollPosition
+ setOwnScrollY(mOwnScrollY - childHeight);
+ } else if (startingPosition < mOwnScrollY) {
+ // This child is currently being scrolled into, set the scroll position to the
+ // start of this child
+ setOwnScrollY(startingPosition);
+ }
}
}
@@ -2888,6 +3255,14 @@
generateAddAnimation(child, false /* fromMoreCard */);
updateAnimationState(child);
updateChronometerForChild(child);
+ if (ANCHOR_SCROLLING) {
+ // TODO: once we're recycling this will need to check the adapter position of the child
+ if (child == getFirstChildNotGone() && (isScrolledToTop() || !mIsExpanded)) {
+ // New child was added at the top while we're scrolled to the top;
+ // make it the new anchor view so that we stay at the top.
+ mScrollAnchorView = child;
+ }
+ }
}
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
@@ -3381,17 +3756,24 @@
final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
if (vscroll != 0) {
final int delta = (int) (vscroll * getVerticalScrollFactor());
- final int range = getScrollRange();
- int oldScrollY = mOwnScrollY;
- int newScrollY = oldScrollY - delta;
- if (newScrollY < 0) {
- newScrollY = 0;
- } else if (newScrollY > range) {
- newScrollY = range;
- }
- if (newScrollY != oldScrollY) {
- setOwnScrollY(newScrollY);
- return true;
+ if (ANCHOR_SCROLLING) {
+ mScrollAnchorViewY -= delta;
+ updateScrollAnchor();
+ clampScrollPosition();
+ updateOnScrollChange();
+ } else {
+ final int range = getScrollRange();
+ int oldScrollY = mOwnScrollY;
+ int newScrollY = oldScrollY - delta;
+ if (newScrollY < 0) {
+ newScrollY = 0;
+ } else if (newScrollY > range) {
+ newScrollY = range;
+ }
+ if (newScrollY != oldScrollY) {
+ setOwnScrollY(newScrollY);
+ return true;
+ }
}
}
}
@@ -3459,12 +3841,16 @@
if (mIsBeingDragged) {
// Scroll to follow the motion event
mLastMotionY = y;
- int range = getScrollRange();
- if (mExpandedInThisMotion) {
- range = Math.min(range, mMaxScrollAfterExpand);
- }
-
float scrollAmount;
+ int range;
+ if (ANCHOR_SCROLLING) {
+ range = 0; // unused in the methods it's being passed to
+ } else {
+ range = getScrollRange();
+ if (mExpandedInThisMotion) {
+ range = Math.min(range, mMaxScrollAfterExpand);
+ }
+ }
if (deltaY < 0) {
scrollAmount = overScrollDown(deltaY);
} else {
@@ -3501,9 +3887,13 @@
onOverScrollFling(false, initialVelocity);
}
} else {
- if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
- getScrollRange())) {
- animateScroll();
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
+ getScrollRange())) {
+ animateScroll();
+ }
}
}
}
@@ -3515,8 +3905,13 @@
break;
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
- if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
- animateScroll();
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
+ getScrollRange())) {
+ animateScroll();
+ }
}
mActivePointerId = INVALID_POINTER;
endDrag();
@@ -3585,12 +3980,6 @@
}
}
- @ShadeViewRefactor(RefactorComponent.INPUT)
- private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
- ev.offsetLocation(sourceView.getX(), sourceView.getY());
- ev.offsetLocation(-targetView.getX(), -targetView.getY());
- }
-
@Override
@ShadeViewRefactor(RefactorComponent.INPUT)
public boolean onInterceptTouchEvent(MotionEvent ev) {
@@ -3763,8 +4152,12 @@
setIsBeingDragged(false);
mActivePointerId = INVALID_POINTER;
recycleVelocityTracker();
- if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
- animateScroll();
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
+ animateScroll();
+ }
}
break;
case MotionEvent.ACTION_POINTER_UP:
@@ -3839,14 +4232,20 @@
case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
// fall through
case android.R.id.accessibilityActionScrollUp:
- final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
- - mShelf.getIntrinsicHeight();
- final int targetScrollY = Math.max(0,
- Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
- if (targetScrollY != mOwnScrollY) {
- mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScrollY - mOwnScrollY);
- animateScroll();
- return true;
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ final int viewportHeight =
+ getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
+ - mShelf.getIntrinsicHeight();
+ final int targetScrollY = Math.max(0,
+ Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
+ if (targetScrollY != mOwnScrollY) {
+ mScroller.startScroll(mScrollX, mOwnScrollY, 0,
+ targetScrollY - mOwnScrollY);
+ animateScroll();
+ return true;
+ }
}
break;
}
@@ -3905,13 +4304,23 @@
@Override
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public boolean isScrolledToTop() {
- return mOwnScrollY == 0;
+ if (ANCHOR_SCROLLING) {
+ updateScrollAnchor();
+ // TODO: once we're recycling this will need to check the adapter position of the child
+ return mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY >= 0;
+ } else {
+ return mOwnScrollY == 0;
+ }
}
@Override
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public boolean isScrolledToBottom() {
- return mOwnScrollY >= getScrollRange();
+ if (ANCHOR_SCROLLING) {
+ return getMaxPositiveScrollAmount() <= 0;
+ } else {
+ return mOwnScrollY >= getScrollRange();
+ }
}
@Override
@@ -3953,7 +4362,7 @@
resetCheckSnoozeLeavebehind();
mAmbientState.setExpansionChanging(false);
if (!mIsExpanded) {
- setOwnScrollY(0);
+ resetScrollPosition();
mStatusBar.resetUserExpandedStates();
clearTemporaryViews();
clearUserLockedViews();
@@ -4012,7 +4421,14 @@
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
public void resetScrollPosition() {
mScroller.abortAnimation();
- setOwnScrollY(0);
+ if (ANCHOR_SCROLLING) {
+ // TODO: once we're recycling this will need to modify the adapter position instead
+ mScrollAnchorView = getFirstChildNotGone();
+ mScrollAnchorViewY = 0;
+ updateOnScrollChange();
+ } else {
+ setOwnScrollY(0);
+ }
}
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
@@ -4081,6 +4497,7 @@
private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
if (view instanceof ExpandableNotificationRow && !onKeyguard()) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ // TODO: once we're recycling this will need to check the adapter position of the child
if (row.isUserLocked() && row != getFirstChildNotGone()) {
if (row.isSummaryWithChildren()) {
return;
@@ -4098,7 +4515,13 @@
layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
}
if (endPosition > layoutEnd) {
- setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
+ if (ANCHOR_SCROLLING) {
+ mScrollAnchorViewY -= (endPosition - layoutEnd);
+ updateScrollAnchor();
+ updateOnScrollChange();
+ } else {
+ setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
+ }
mDisallowScrollingInThisMotion = true;
}
}
@@ -4663,9 +5086,13 @@
super.onInitializeAccessibilityEventInternal(event);
event.setScrollable(mScrollable);
event.setScrollX(mScrollX);
- event.setScrollY(mOwnScrollY);
event.setMaxScrollX(mScrollX);
- event.setMaxScrollY(getScrollRange());
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ event.setScrollY(mOwnScrollY);
+ event.setMaxScrollY(getScrollRange());
+ }
}
@Override
@@ -4850,13 +5277,63 @@
}
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
- public void setOwnScrollY(int ownScrollY) {
+ private void setOwnScrollY(int ownScrollY) {
+ assert !ANCHOR_SCROLLING;
if (ownScrollY != mOwnScrollY) {
// We still want to call the normal scrolled changed for accessibility reasons
onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY);
mOwnScrollY = ownScrollY;
- updateForwardAndBackwardScrollability();
- requestChildrenUpdate();
+ updateOnScrollChange();
+ }
+ }
+
+ private void updateOnScrollChange() {
+ updateForwardAndBackwardScrollability();
+ requestChildrenUpdate();
+ }
+
+ private void updateScrollAnchor() {
+ int anchorIndex = indexOfChild(mScrollAnchorView);
+ // If the anchor view has been scrolled off the top, move to the next view.
+ while (mScrollAnchorViewY < 0) {
+ View nextAnchor = null;
+ for (int i = anchorIndex + 1; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != View.GONE
+ && child instanceof ExpandableNotificationRow) {
+ anchorIndex = i;
+ nextAnchor = child;
+ break;
+ }
+ }
+ if (nextAnchor == null) {
+ break;
+ }
+ mScrollAnchorViewY +=
+ (int) (nextAnchor.getTranslationY() - mScrollAnchorView.getTranslationY());
+ mScrollAnchorView = nextAnchor;
+ }
+ // If the view above the anchor view is fully visible, make it the anchor view.
+ while (anchorIndex > 0 && mScrollAnchorViewY > 0) {
+ View prevAnchor = null;
+ for (int i = anchorIndex - 1; i >= 0; i--) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != View.GONE
+ && child instanceof ExpandableNotificationRow) {
+ anchorIndex = i;
+ prevAnchor = child;
+ break;
+ }
+ }
+ if (prevAnchor == null) {
+ break;
+ }
+ float distanceToPreviousAnchor =
+ mScrollAnchorView.getTranslationY() - prevAnchor.getTranslationY();
+ if (distanceToPreviousAnchor < mScrollAnchorViewY) {
+ mScrollAnchorViewY -= (int) distanceToPreviousAnchor;
+ mScrollAnchorView = prevAnchor;
+ }
}
}
@@ -4872,6 +5349,9 @@
mAmbientState.setShelf(shelf);
mStateAnimator.setShelf(shelf);
shelf.bind(mAmbientState, this);
+ if (ANCHOR_SCROLLING) {
+ mScrollAnchorView = mShelf;
+ }
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -5818,6 +6298,15 @@
}
@Override
+ public int getConstrainSwipeStartPosition() {
+ NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
+ if (menuRow != null) {
+ return Math.abs(menuRow.getMenuSnapTarget());
+ }
+ return 0;
+ }
+
+ @Override
public boolean canChildBeDismissed(View v) {
return NotificationStackScrollLayout.this.canChildBeDismissed(v);
}
@@ -6013,7 +6502,11 @@
public void expansionStateChanged(boolean isExpanding) {
mExpandingNotification = isExpanding;
if (!mExpandedInThisMotion) {
- mMaxScrollAfterExpand = mOwnScrollY;
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ mMaxScrollAfterExpand = mOwnScrollY;
+ }
mExpandedInThisMotion = true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 25fb7f9..2a88080 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -41,6 +41,8 @@
*/
public class StackScrollAlgorithm {
+ static final boolean ANCHOR_SCROLLING = false;
+
private static final String LOG_TAG = "StackScrollAlgorithm";
private final ViewGroup mHostView;
@@ -236,6 +238,10 @@
scrollY = Math.max(0, scrollY);
state.scrollY = (int) (scrollY + bottomOverScroll);
+ if (ANCHOR_SCROLLING) {
+ state.anchorViewY = (int) (ambientState.getAnchorViewY() - bottomOverScroll);
+ }
+
//now init the visible children and update paddings
int childCount = hostView.getChildCount();
state.visibleChildren.clear();
@@ -252,6 +258,11 @@
// iterating over it again, it's filled with the actual resolved value.
for (int i = 0; i < childCount; i++) {
+ if (ANCHOR_SCROLLING) {
+ if (i == ambientState.getAnchorViewIndex()) {
+ state.anchorViewIndex = state.visibleChildren.size();
+ }
+ }
ExpandableView v = (ExpandableView) hostView.getChildAt(i);
if (v.getVisibility() != View.GONE) {
if (v == ambientState.getShelf()) {
@@ -350,28 +361,74 @@
private void updatePositionsForState(StackScrollAlgorithmState algorithmState,
AmbientState ambientState) {
- // The y coordinate of the current child.
- float currentYPosition = -algorithmState.scrollY;
- int childCount = algorithmState.visibleChildren.size();
- for (int i = 0; i < childCount; i++) {
- currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition);
+ if (ANCHOR_SCROLLING) {
+ float currentYPosition = algorithmState.anchorViewY;
+ int childCount = algorithmState.visibleChildren.size();
+ for (int i = algorithmState.anchorViewIndex; i < childCount; i++) {
+ if (i > algorithmState.anchorViewIndex && ambientState.beginsNewSection(i)) {
+ currentYPosition += mGapHeight;
+ }
+ currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
+ false /* reverse */);
+ }
+ currentYPosition = algorithmState.anchorViewY;
+ for (int i = algorithmState.anchorViewIndex - 1; i >= 0; i--) {
+ if (ambientState.beginsNewSection(i + 1)) {
+ currentYPosition -= mGapHeight;
+ }
+ currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
+ true /* reverse */);
+ }
+ } else {
+ // The y coordinate of the current child.
+ float currentYPosition = -algorithmState.scrollY;
+ int childCount = algorithmState.visibleChildren.size();
+ for (int i = 0; i < childCount; i++) {
+ if (ambientState.beginsNewSection(i)) {
+ currentYPosition += mGapHeight;
+ }
+ currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
+ false /* reverse */);
+ }
}
}
+ /**
+ * Populates the {@link ExpandableViewState} for a single child.
+ *
+ * @param i The index of the child in
+ * {@link StackScrollAlgorithmState#visibleChildren}.
+ * @param algorithmState The overall output state of the algorithm.
+ * @param ambientState The input state provided to the algorithm.
+ * @param currentYPosition The Y position of the current pass of the algorithm. For a forward
+ * pass, this should be the top of the child; for a reverse pass, the
+ * bottom of the child.
+ * @param reverse Whether we're laying out children in the reverse direction (Y
+ * positions
+ * decreasing) instead of the forward direction (Y positions
+ * increasing).
+ * @return The Y position after laying out the child. This will be the {@code currentYPosition}
+ * for the next call to this method, after adjusting for any gaps between children.
+ */
protected float updateChild(
int i,
StackScrollAlgorithmState algorithmState,
AmbientState ambientState,
- float currentYPosition) {
+ float currentYPosition,
+ boolean reverse) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = child.getViewState();
childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
int paddingAfterChild = getPaddingAfterChild(algorithmState, child);
int childHeight = getMaxAllowedChildHeight(child);
- if (ambientState.beginsNewSection(i)) {
- currentYPosition += mGapHeight;
+ if (reverse) {
+ childViewState.yTranslation = currentYPosition - (childHeight + paddingAfterChild);
+ if (currentYPosition <= 0) {
+ childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
+ }
+ } else {
+ childViewState.yTranslation = currentYPosition;
}
- childViewState.yTranslation = currentYPosition;
boolean isFooterView = child instanceof FooterView;
boolean isEmptyShadeView = child instanceof EmptyShadeView;
@@ -396,9 +453,13 @@
clampPositionToShelf(child, childViewState, ambientState);
}
- currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
- if (currentYPosition <= 0) {
- childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
+ if (reverse) {
+ currentYPosition = childViewState.yTranslation;
+ } else {
+ currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
+ if (currentYPosition <= 0) {
+ childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
+ }
}
if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) {
Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
@@ -464,6 +525,7 @@
// To check if the row need to do translation according to scroll Y
// heads up show full of row's content and any scroll y indicate that the
// translationY need to move up the HUN.
+ // TODO: fix this check for anchor scrolling.
if (!mIsExpanded && isTopEntry && ambientState.getScrollY() > 0) {
childState.yTranslation -= ambientState.getScrollY();
}
@@ -607,10 +669,18 @@
public class StackScrollAlgorithmState {
/**
- * The scroll position of the algorithm
+ * The scroll position of the algorithm (absolute scrolling).
*/
public int scrollY;
+ /** The index of the anchor view (anchor scrolling). */
+ public int anchorViewIndex;
+
+ /**
+ * The Y position, relative to the top of the screen, of the anchor view (anchor scrolling).
+ */
+ public int anchorViewY;
+
/**
* The children from the host view which are not gone.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index d3c6a1d..ee047e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -243,6 +243,9 @@
mDisabledFlags2 = savedInstanceState.getInt(EXTRA_DISABLE2_STATE, 0);
}
mAccessibilityManagerWrapper.addCallback(mAccessibilityListener);
+
+ // Respect the latest disabled-flags.
+ mCommandQueue.recomputeDisableFlags(mDisplayId, false);
}
@Override
@@ -799,7 +802,9 @@
}
private void onAccessibilityClick(View v) {
- mAccessibilityManager.notifyAccessibilityButtonClicked();
+ final Display display = v.getDisplay();
+ mAccessibilityManager.notifyAccessibilityButtonClicked(
+ display != null ? display.getDisplayId() : Display.DEFAULT_DISPLAY);
}
private boolean onAccessibilityLongClick(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
index 2a11c26..d022808 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
@@ -97,10 +97,11 @@
}
return mClickableChildren
.stream()
- .filter(v -> v.isAttachedToWindow())
+ .filter(View::isAttachedToWindow)
.map(v -> new Pair<>(distance(v, event), v))
.min(Comparator.comparingInt(f -> f.first))
- .get().second;
+ .map(data -> data.second)
+ .orElse(null);
}
private int distance(View v, MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 4f61009..86326be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -64,6 +64,7 @@
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -304,8 +305,11 @@
final int count =
mEntryManager.getNotificationData().getActiveNotifications().size();
final int rank = mEntryManager.getNotificationData().getRank(notificationKey);
+ NotificationVisibility.NotificationLocation location =
+ NotificationLogger.getNotificationLocation(
+ mEntryManager.getNotificationData().get(notificationKey));
final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
- rank, count, true);
+ rank, count, true, location);
try {
mBarService.onNotificationClick(notificationKey, nv);
} catch (RemoteException ex) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index 88f9048..ffaa236 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -69,13 +69,13 @@
private final WindowManager mWindowManager;
private final IActivityManager mActivityManager;
private final DozeParameters mDozeParameters;
+ private final WindowManager.LayoutParams mLpChanged;
+ private final boolean mKeyguardScreenRotation;
private ViewGroup mStatusBarView;
private WindowManager.LayoutParams mLp;
- private WindowManager.LayoutParams mLpChanged;
private boolean mHasTopUi;
private boolean mHasTopUiChanged;
private int mBarHeight;
- private final boolean mKeyguardScreenRotation;
private float mScreenBrightnessDoze;
private final State mCurrentState = new State();
private OtherwisedCollapsedListener mListener;
@@ -97,6 +97,7 @@
mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation();
mDozeParameters = dozeParameters;
mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
+ mLpChanged = new WindowManager.LayoutParams();
Dependency.get(StatusBarStateController.class).addCallback(
mStateListener, StatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
Dependency.get(ConfigurationController.class).addCallback(this);
@@ -138,7 +139,6 @@
mStatusBarView = statusBarView;
mBarHeight = barHeight;
mWindowManager.addView(mStatusBarView, mLp);
- mLpChanged = new WindowManager.LayoutParams();
mLpChanged.copyFrom(mLp);
onThemeChanged();
}
@@ -228,7 +228,9 @@
private void applyHeight(State state) {
boolean expanded = isExpanded(state);
if (state.forcePluginOpen) {
- mListener.setWouldOtherwiseCollapse(expanded);
+ if (mListener != null) {
+ mListener.setWouldOtherwiseCollapse(expanded);
+ }
expanded = true;
}
if (expanded) {
@@ -247,7 +249,7 @@
private void applyFitsSystemWindows(State state) {
boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
- if (mStatusBarView.getFitsSystemWindows() != fitsSystemWindows) {
+ if (mStatusBarView != null && mStatusBarView.getFitsSystemWindows() != fitsSystemWindows) {
mStatusBarView.setFitsSystemWindows(fitsSystemWindows);
mStatusBarView.requestApplyInsets();
}
@@ -289,7 +291,7 @@
applyBrightness(state);
applyHasTopUi(state);
applyNotTouchable(state);
- if (mLp.copyFrom(mLpChanged) != 0) {
+ if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
mWindowManager.updateViewLayout(mStatusBarView, mLp);
}
if (mHasTopUi != mHasTopUiChanged) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 9c4db34..7881df9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -188,6 +188,7 @@
LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
v.mController = controller;
v.mEntry = entry;
+ v.mEditText.setTextOperationUser(computeTextOperationUser(entry.notification.getUser()));
v.setTag(VIEW_TAG);
return v;
@@ -298,7 +299,6 @@
if (mWrapper != null) {
mWrapper.setRemoteInputVisible(true);
}
- mEditText.setTextOperationUser(computeTextOperationUser(mEntry.notification.getUser()));
mEditText.setInnerFocusable(true);
mEditText.mShowImeOnInputConnection = true;
mEditText.setText(mEntry.remoteInputText);
@@ -328,7 +328,6 @@
mResetting = true;
mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
- mEditText.setTextOperationUser(null);
mEditText.getText().clear();
mEditText.setEnabled(true);
mSendButton.setVisibility(VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 3bd0d45..db04620 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -46,18 +46,21 @@
private static final String KEY_EDIT_CHOICES_BEFORE_SENDING =
"edit_choices_before_sending";
private static final String KEY_SHOW_IN_HEADS_UP = "show_in_heads_up";
+ private static final String KEY_MIN_NUM_REPLIES = "min_num_system_generated_replies";
private final boolean mDefaultEnabled;
private final boolean mDefaultRequiresP;
private final int mDefaultMaxSqueezeRemeasureAttempts;
private final boolean mDefaultEditChoicesBeforeSending;
private final boolean mDefaultShowInHeadsUp;
+ private final int mDefaultMinNumSystemGeneratedReplies;
private boolean mEnabled;
private boolean mRequiresTargetingP;
private int mMaxSqueezeRemeasureAttempts;
private boolean mEditChoicesBeforeSending;
private boolean mShowInHeadsUp;
+ private int mMinNumSystemGeneratedReplies;
private final Context mContext;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -78,6 +81,8 @@
R.bool.config_smart_replies_in_notifications_edit_choices_before_sending);
mDefaultShowInHeadsUp = resources.getBoolean(
R.bool.config_smart_replies_in_notifications_show_in_heads_up);
+ mDefaultMinNumSystemGeneratedReplies = resources.getInteger(
+ R.integer.config_smart_replies_in_notifications_min_num_system_generated_replies);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS),
@@ -105,6 +110,8 @@
mEditChoicesBeforeSending = mParser.getBoolean(
KEY_EDIT_CHOICES_BEFORE_SENDING, mDefaultEditChoicesBeforeSending);
mShowInHeadsUp = mParser.getBoolean(KEY_SHOW_IN_HEADS_UP, mDefaultShowInHeadsUp);
+ mMinNumSystemGeneratedReplies =
+ mParser.getInt(KEY_MIN_NUM_REPLIES, mDefaultMinNumSystemGeneratedReplies);
}
}
@@ -155,4 +162,12 @@
public boolean getShowInHeadsUp() {
return mShowInHeadsUp;
}
+
+ /**
+ * Returns the minimum number of system generated replies to show in a notification.
+ * If we cannot show at least this many system generated replies we should show none.
+ */
+ public int getMinNumSystemGeneratedReplies() {
+ return mMinNumSystemGeneratedReplies;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index d6eff94..c4f027f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -88,6 +88,12 @@
private View mSmartReplyContainer;
+ /**
+ * Whether the smart replies in this view were generated by the notification assistant. If not
+ * they're provided by the app.
+ */
+ private boolean mSmartRepliesGeneratedByAssistant = false;
+
@ColorInt
private int mCurrentBackgroundColor;
@ColorInt
@@ -202,6 +208,7 @@
getContext(), this, i, smartReplies, smartReplyController, entry);
addView(replyButton);
}
+ this.mSmartRepliesGeneratedByAssistant = smartReplies.fromAssistant;
}
}
reallocateCandidateButtonQueueForSqueezing();
@@ -344,10 +351,11 @@
mCandidateButtonQueueForSqueezing.clear();
}
- int measuredWidth = mPaddingLeft + mPaddingRight;
- int maxChildHeight = 0;
+ SmartSuggestionMeasures accumulatedMeasures = new SmartSuggestionMeasures(
+ mPaddingLeft + mPaddingRight,
+ 0 /* maxChildHeight */,
+ mSingleLineButtonPaddingHorizontal);
int displayedChildCount = 0;
- int buttonPaddingHorizontal = mSingleLineButtonPaddingHorizontal;
// Set up a list of suggestions where actions come before replies. Note that the Buttons
// themselves have already been added to the view hierarchy in an order such that Smart
@@ -360,11 +368,15 @@
smartSuggestions.addAll(smartReplies);
List<View> coveredSuggestions = new ArrayList<>();
+ // SmartSuggestionMeasures for all action buttons, this will be filled in when the first
+ // reply button is added.
+ SmartSuggestionMeasures actionsMeasures = null;
+
for (View child : smartSuggestions) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- child.setPadding(buttonPaddingHorizontal, child.getPaddingTop(),
- buttonPaddingHorizontal, child.getPaddingBottom());
+ child.setPadding(accumulatedMeasures.mButtonPaddingHorizontal, child.getPaddingTop(),
+ accumulatedMeasures.mButtonPaddingHorizontal, child.getPaddingBottom());
child.measure(MEASURE_SPEC_ANY_LENGTH, heightMeasureSpec);
coveredSuggestions.add(child);
@@ -380,45 +392,52 @@
}
// Remember the current measurements in case the current button doesn't fit in.
- final int originalMaxChildHeight = maxChildHeight;
- final int originalMeasuredWidth = measuredWidth;
- final int originalButtonPaddingHorizontal = buttonPaddingHorizontal;
+ SmartSuggestionMeasures originalMeasures = accumulatedMeasures.clone();
+ if (actionsMeasures == null && lp.buttonType == SmartButtonType.REPLY) {
+ // We've added all actions (we go through actions first), now add their
+ // measurements.
+ actionsMeasures = accumulatedMeasures.clone();
+ }
final int spacing = displayedChildCount == 0 ? 0 : mSpacing;
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
- measuredWidth += spacing + childWidth;
- maxChildHeight = Math.max(maxChildHeight, childHeight);
+ accumulatedMeasures.mMeasuredWidth += spacing + childWidth;
+ accumulatedMeasures.mMaxChildHeight =
+ Math.max(accumulatedMeasures.mMaxChildHeight, childHeight);
// Do we need to increase the number of lines in smart reply buttons to two?
final boolean increaseToTwoLines =
- buttonPaddingHorizontal == mSingleLineButtonPaddingHorizontal
- && (lineCount == 2 || measuredWidth > targetWidth);
+ (accumulatedMeasures.mButtonPaddingHorizontal
+ == mSingleLineButtonPaddingHorizontal)
+ && (lineCount == 2 || accumulatedMeasures.mMeasuredWidth > targetWidth);
if (increaseToTwoLines) {
- measuredWidth += (displayedChildCount + 1) * mSingleToDoubleLineButtonWidthIncrease;
- buttonPaddingHorizontal = mDoubleLineButtonPaddingHorizontal;
+ accumulatedMeasures.mMeasuredWidth +=
+ (displayedChildCount + 1) * mSingleToDoubleLineButtonWidthIncrease;
+ accumulatedMeasures.mButtonPaddingHorizontal =
+ mDoubleLineButtonPaddingHorizontal;
}
// If the last button doesn't fit into the remaining width, try squeezing preceding
// smart reply buttons.
- if (measuredWidth > targetWidth) {
+ if (accumulatedMeasures.mMeasuredWidth > targetWidth) {
// Keep squeezing preceding and current smart reply buttons until they all fit.
- while (measuredWidth > targetWidth
+ while (accumulatedMeasures.mMeasuredWidth > targetWidth
&& !mCandidateButtonQueueForSqueezing.isEmpty()) {
final Button candidate = mCandidateButtonQueueForSqueezing.poll();
final int squeezeReduction = squeezeButton(candidate, heightMeasureSpec);
if (squeezeReduction != SQUEEZE_FAILED) {
- maxChildHeight = Math.max(maxChildHeight, candidate.getMeasuredHeight());
- measuredWidth -= squeezeReduction;
+ accumulatedMeasures.mMaxChildHeight =
+ Math.max(accumulatedMeasures.mMaxChildHeight,
+ candidate.getMeasuredHeight());
+ accumulatedMeasures.mMeasuredWidth -= squeezeReduction;
}
}
// If the current button still doesn't fit after squeezing all buttons, undo the
// last squeezing round.
- if (measuredWidth > targetWidth) {
- measuredWidth = originalMeasuredWidth;
- maxChildHeight = originalMaxChildHeight;
- buttonPaddingHorizontal = originalButtonPaddingHorizontal;
+ if (accumulatedMeasures.mMeasuredWidth > targetWidth) {
+ accumulatedMeasures = originalMeasures;
// Mark all buttons from the last squeezing round as "failed to squeeze", so
// that they're re-measured without squeezing later.
@@ -440,16 +459,75 @@
displayedChildCount++;
}
+ if (mSmartRepliesGeneratedByAssistant) {
+ if (!gotEnoughSmartReplies(smartReplies)) {
+ // We don't have enough smart replies - hide all of them.
+ for (View smartReplyButton : smartReplies) {
+ final LayoutParams lp = (LayoutParams) smartReplyButton.getLayoutParams();
+ lp.show = false;
+ }
+ // Reset our measures back to when we had only added actions (before adding
+ // replies).
+ accumulatedMeasures = actionsMeasures;
+ }
+ }
+
// We're done squeezing buttons, so we can clear the priority queue.
mCandidateButtonQueueForSqueezing.clear();
// Finally, we need to re-measure some buttons.
- remeasureButtonsIfNecessary(buttonPaddingHorizontal, maxChildHeight);
+ remeasureButtonsIfNecessary(accumulatedMeasures.mButtonPaddingHorizontal,
+ accumulatedMeasures.mMaxChildHeight);
setMeasuredDimension(
- resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth), widthMeasureSpec),
- resolveSize(Math.max(getSuggestedMinimumHeight(),
- mPaddingTop + maxChildHeight + mPaddingBottom), heightMeasureSpec));
+ resolveSize(Math.max(getSuggestedMinimumWidth(),
+ accumulatedMeasures.mMeasuredWidth),
+ widthMeasureSpec),
+ resolveSize(Math.max(getSuggestedMinimumHeight(), mPaddingTop
+ + accumulatedMeasures.mMaxChildHeight + mPaddingBottom),
+ heightMeasureSpec));
+ }
+
+ /**
+ * Fields we keep track of inside onMeasure() to correctly measure the SmartReplyView depending
+ * on which suggestions are added.
+ */
+ private static class SmartSuggestionMeasures {
+ int mMeasuredWidth = -1;
+ int mMaxChildHeight = -1;
+ int mButtonPaddingHorizontal = -1;
+
+ SmartSuggestionMeasures(int measuredWidth, int maxChildHeight,
+ int buttonPaddingHorizontal) {
+ this.mMeasuredWidth = measuredWidth;
+ this.mMaxChildHeight = maxChildHeight;
+ this.mButtonPaddingHorizontal = buttonPaddingHorizontal;
+ }
+
+ public SmartSuggestionMeasures clone() {
+ return new SmartSuggestionMeasures(
+ mMeasuredWidth, mMaxChildHeight, mButtonPaddingHorizontal);
+ }
+ }
+
+ /**
+ * Returns whether our notification contains at least N smart replies (or 0) where N is
+ * determined by {@link SmartReplyConstants}.
+ */
+ private boolean gotEnoughSmartReplies(List<View> smartReplies) {
+ int numShownReplies = 0;
+ for (View smartReplyButton : smartReplies) {
+ final LayoutParams lp = (LayoutParams) smartReplyButton.getLayoutParams();
+ if (lp.show) {
+ numShownReplies++;
+ }
+ }
+ if (numShownReplies == 0
+ || numShownReplies >= mConstants.getMinNumSystemGeneratedReplies()) {
+ // We have enough replies, yay!
+ return true;
+ }
+ return false;
}
private List<View> filterActionsOrReplies(SmartButtonType buttonType) {
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java
new file mode 100644
index 0000000..fa4b3fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java
@@ -0,0 +1,73 @@
+/*
+ * 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.usb;
+
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.hardware.usb.ParcelableUsbPort;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.os.Bundle;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.systemui.R;
+
+/**
+ * Activity that alerts the user when contaminant is detected on USB port.
+ */
+public class UsbContaminantActivity extends AlertActivity
+ implements DialogInterface.OnClickListener {
+ private static final String TAG = "UsbContaminantActivity";
+
+ private UsbDisconnectedReceiver mDisconnectedReceiver;
+ private UsbPort mUsbPort;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ Window window = getWindow();
+ window.addSystemFlags(WindowManager.LayoutParams
+ .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+
+ super.onCreate(icicle);
+
+ Intent intent = getIntent();
+ ParcelableUsbPort port = intent.getParcelableExtra(UsbManager.EXTRA_PORT);
+ mUsbPort = port.getUsbPort(getSystemService(UsbManager.class));
+
+ final AlertController.AlertParams ap = mAlertParams;
+ ap.mTitle = getString(R.string.usb_contaminant_title);
+ ap.mMessage = getString(R.string.usb_contaminant_message);
+ ap.mPositiveButtonText = getString(android.R.string.ok);
+ ap.mPositiveButtonListener = this;
+
+ setupAlert();
+ }
+
+ @Override
+ public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
+ super.onWindowAttributesChanged(params);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
index ed2ad79..12006fa 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
@@ -75,7 +75,7 @@
final AlertController.AlertParams ap = mAlertParams;
ap.mTitle = getString(R.string.usb_debugging_title);
ap.mMessage = getString(R.string.usb_debugging_message, fingerprints);
- ap.mPositiveButtonText = getString(android.R.string.ok);
+ ap.mPositiveButtonText = getString(R.string.usb_debugging_allow);
ap.mNegativeButtonText = getString(android.R.string.cancel);
ap.mPositiveButtonListener = this;
ap.mNegativeButtonListener = this;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
index 712ea27..8b00eee 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
@@ -25,9 +25,9 @@
import android.content.pm.ResolveInfo;
import android.media.IRemoteVolumeController;
import android.media.MediaMetadata;
-import android.media.session.ISessionController;
import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession;
import android.media.session.MediaSession.QueueItem;
import android.media.session.MediaSession.Token;
import android.media.session.MediaSessionManager;
@@ -113,17 +113,17 @@
r.controller.setVolumeTo(level, 0);
}
- private void onRemoteVolumeChangedH(ISessionController session, int flags) {
- final MediaController controller = new MediaController(mContext, session);
+ private void onRemoteVolumeChangedH(MediaSession.Token sessionToken, int flags) {
+ final MediaController controller = new MediaController(mContext, sessionToken);
if (D.BUG) Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " "
+ Util.audioManagerFlagsToString(flags));
final Token token = controller.getSessionToken();
mCallbacks.onRemoteVolumeChanged(token, flags);
}
- private void onUpdateRemoteControllerH(ISessionController session) {
- final MediaController controller = session != null ? new MediaController(mContext, session)
- : null;
+ private void onUpdateRemoteControllerH(MediaSession.Token sessionToken) {
+ final MediaController controller =
+ sessionToken != null ? new MediaController(mContext, sessionToken) : null;
final String pkg = controller != null ? controller.getPackageName() : null;
if (D.BUG) Log.d(TAG, "updateRemoteControllerH " + pkg);
// this may be our only indication that a remote session is changed, refresh
@@ -332,15 +332,16 @@
private final IRemoteVolumeController mRvc = new IRemoteVolumeController.Stub() {
@Override
- public void remoteVolumeChanged(ISessionController session, int flags)
+ public void remoteVolumeChanged(MediaSession.Token sessionToken, int flags)
throws RemoteException {
- mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0, session).sendToTarget();
+ mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0,
+ sessionToken).sendToTarget();
}
@Override
- public void updateRemoteController(final ISessionController session)
+ public void updateRemoteController(final MediaSession.Token sessionToken)
throws RemoteException {
- mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, session).sendToTarget();
+ mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, sessionToken).sendToTarget();
}
};
@@ -360,10 +361,10 @@
onActiveSessionsUpdatedH(mMgr.getActiveSessions(null));
break;
case REMOTE_VOLUME_CHANGED:
- onRemoteVolumeChangedH((ISessionController) msg.obj, msg.arg1);
+ onRemoteVolumeChangedH((MediaSession.Token) msg.obj, msg.arg1);
break;
case UPDATE_REMOTE_CONTROLLER:
- onUpdateRemoteControllerH((ISessionController) msg.obj);
+ onUpdateRemoteControllerH((MediaSession.Token) msg.obj);
break;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index fbc1c20..d80b444 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -39,6 +39,7 @@
import android.widget.FrameLayout;
import android.widget.TextClock;
+import com.android.keyguard.clock.ClockManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.statusbar.StatusBarState;
@@ -51,8 +52,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.function.Consumer;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
// Need to run on the main thread because KeyguardSliceView$Row init checks for
@@ -85,7 +84,7 @@
TextClock pluginView = new TextClock(getContext());
when(plugin.getView()).thenReturn(pluginView);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
verify(mClockView).setVisibility(GONE);
assertThat(plugin.getView().getParent()).isEqualTo(mClockContainer);
@@ -102,7 +101,7 @@
TextClock pluginView = new TextClock(getContext());
when(plugin.getBigClockView()).thenReturn(pluginView);
// WHEN the plugin is connected
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
// THEN the big clock container is visible and it is the parent of the
// big clock view.
assertThat(bigClockContainer.getVisibility()).isEqualTo(VISIBLE);
@@ -112,7 +111,7 @@
@Test
public void onPluginConnected_nullView() {
ClockPlugin plugin = mock(ClockPlugin.class);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
verify(mClockView, never()).setVisibility(GONE);
}
@@ -121,11 +120,11 @@
// GIVEN a plugin has already connected
ClockPlugin plugin1 = mock(ClockPlugin.class);
when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin1);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin1);
// WHEN a second plugin is connected
ClockPlugin plugin2 = mock(ClockPlugin.class);
when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin2);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin2);
// THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer);
assertThat(plugin1.getView().getParent()).isNull();
@@ -137,7 +136,7 @@
mKeyguardClockSwitch.setDarkAmount(0.5f);
// WHEN a plugin is connected
ClockPlugin plugin = mock(ClockPlugin.class);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
// THEN dark amount should be initalized on the plugin.
verify(plugin).setDarkAmount(0.5f);
}
@@ -149,8 +148,8 @@
when(plugin.getView()).thenReturn(pluginView);
mClockView.setVisibility(GONE);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null);
verify(mClockView).setVisibility(VISIBLE);
assertThat(plugin.getView().getParent()).isNull();
@@ -167,8 +166,8 @@
TextClock pluginView = new TextClock(getContext());
when(plugin.getBigClockView()).thenReturn(pluginView);
// WHEN the plugin is connected and then disconnected
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null);
// THEN the big lock container is GONE and the big clock view doesn't have
// a parent.
assertThat(bigClockContainer.getVisibility()).isEqualTo(GONE);
@@ -178,8 +177,8 @@
@Test
public void onPluginDisconnected_nullView() {
ClockPlugin plugin = mock(ClockPlugin.class);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null);
verify(mClockView, never()).setVisibility(GONE);
}
@@ -188,13 +187,13 @@
// GIVEN two plugins are connected
ClockPlugin plugin1 = mock(ClockPlugin.class);
when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- Consumer<ClockPlugin> consumer = mKeyguardClockSwitch.getClockPluginConsumer();
- consumer.accept(plugin1);
+ ClockManager.ClockChangedListener listener = mKeyguardClockSwitch.getClockChangedListener();
+ listener.onClockChanged(plugin1);
ClockPlugin plugin2 = mock(ClockPlugin.class);
when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- consumer.accept(plugin2);
+ listener.onClockChanged(plugin2);
// WHEN the second plugin is disconnected
- consumer.accept(null);
+ listener.onClockChanged(null);
// THEN the default clock should be shown.
verify(mClockView).setVisibility(VISIBLE);
assertThat(plugin1.getView().getParent()).isNull();
@@ -213,7 +212,7 @@
ClockPlugin plugin = mock(ClockPlugin.class);
TextClock pluginView = new TextClock(getContext());
when(plugin.getView()).thenReturn(pluginView);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
mKeyguardClockSwitch.setTextColor(Color.WHITE);
@@ -237,7 +236,7 @@
TextClock pluginView = new TextClock(getContext());
when(plugin.getView()).thenReturn(pluginView);
Style style = mock(Style.class);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
mKeyguardClockSwitch.setStyle(style);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 53ad0b5..fc57909 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -68,7 +68,7 @@
public Engine onCreateEngine() {
return new DrawableEngine() {
@Override
- DisplayInfo getDefaultDisplayInfo() {
+ DisplayInfo getDisplayInfo() {
return mDisplayInfo;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index fa5cf04..60a20cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.bubbles;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.atLeastOnce;
@@ -160,6 +161,12 @@
stackView.expandStack();
assertTrue(mBubbleController.isStackExpanded());
+ stackView.setExpandedBubble(mRow.getEntry());
+ assertEquals(stackView.getExpandedBubble().getEntry(), mRow.getEntry());
+
+ stackView.setExpandedBubble(mRow2.getEntry());
+ assertEquals(stackView.getExpandedBubble().getEntry(), mRow2.getEntry());
+
mBubbleController.collapseStack();
assertFalse(mBubbleController.isStackExpanded());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java b/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
index b368876..839b5e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
@@ -32,6 +32,11 @@
this.mCallback = null;
}
+ @Override
+ public boolean isDocked() {
+ return false;
+ }
+
public void setDockEvent(int event) {
mCallback.onEvent(event);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
index f45500a..e4558df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
@@ -36,6 +36,8 @@
when(params.getPickupVibrationThreshold()).thenReturn(0);
when(params.getProxCheckBeforePulse()).thenReturn(true);
when(params.getPickupSubtypePerformsProxCheck(anyInt())).thenReturn(true);
+ when(params.getPolicy()).thenReturn(mock(AlwaysOnDisplayPolicy.class));
+ when(params.doubleTapReportsTouchCoordinates()).thenReturn(false);
doneHolder[0] = true;
return params;
@@ -48,9 +50,14 @@
when(config.doubleTapGestureEnabled(anyInt())).thenReturn(false);
when(config.pickupGestureEnabled(anyInt())).thenReturn(false);
when(config.pulseOnNotificationEnabled(anyInt())).thenReturn(true);
+ when(config.alwaysOnEnabled(anyInt())).thenReturn(false);
when(config.doubleTapSensorType()).thenReturn(null);
+ when(config.tapSensorType()).thenReturn(null);
+ when(config.longPressSensorType()).thenReturn(null);
+
when(config.dozePickupSensorAvailable()).thenReturn(false);
+ when(config.wakeScreenGestureAvailable()).thenReturn(false);
doneHolder[0] = true;
return config;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
index 926ff69..0fc0953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
@@ -75,7 +75,7 @@
mContext.putComponent(DockManager.class, mDockManagerFake);
mDockHandler = new DozeDockHandler(mContext, mMachine, mHost, mConfig,
- Handler.createAsync(Looper.myLooper()));
+ Handler.createAsync(Looper.myLooper()), mDockManagerFake);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 31fc625..7b358b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -18,8 +18,10 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -34,6 +36,8 @@
import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.wakelock.WakeLock;
import com.android.systemui.util.wakelock.WakeLockFake;
@@ -59,6 +63,7 @@
private WakeLock mWakeLock;
private Instrumentation mInstrumentation;
private AlarmManager mAlarmManager;
+ private DockManagerFake mDockManagerFake;
@BeforeClass
public static void setupSuite() {
@@ -76,9 +81,12 @@
mParameters = DozeConfigurationUtil.createMockParameters();
mSensors = new FakeSensorManager(mContext);
mWakeLock = new WakeLockFake();
+ mDockManagerFake = spy(new DockManagerFake());
+ mContext.putComponent(DockManager.class, mDockManagerFake);
mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, mConfig, mParameters,
- mSensors, Handler.createAsync(Looper.myLooper()), mWakeLock, true);
+ mSensors, Handler.createAsync(Looper.myLooper()), mWakeLock, true,
+ mDockManagerFake);
}
@Test
@@ -102,4 +110,38 @@
verify(mMachine).requestPulse(anyInt());
}
+ @Test
+ public void testDockEventListener_registerAndUnregister() {
+ mTriggers.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
+
+ verify(mDockManagerFake).addListener(any());
+
+ mTriggers.transitionTo(DozeMachine.State.DOZE, DozeMachine.State.FINISH);
+
+ verify(mDockManagerFake).removeListener(any());
+ }
+
+ @Test
+ public void testOnSensor_whenUndockedWithNearAndDoubleTapScreen_shouldNotWakeUp() {
+ mSensors.getMockProximitySensor().sendProximityResult(false /* far */);
+
+ mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP,
+ false /* sensorPerformedProxCheck */, 50 /* screenX */, 50 /* screenY */,
+ null /* rawValues */);
+
+ verify(mMachine, never()).wakeUp();
+ }
+
+ @Test
+ public void testOnSensor_whenDockedWithNearAndDoubleTapScreen_shouldWakeUp() {
+ doReturn(true).when(mDockManagerFake).isDocked();
+ mSensors.getMockProximitySensor().sendProximityResult(false /* far */);
+
+ mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP,
+ false /* sensorPerformedProxCheck */, 50 /* screenX */, 50 /* screenY */,
+ null /* rawValues */);
+
+ verify(mMachine).wakeUp();
+ }
+
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
index 4b03399..5ff9d14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
@@ -17,6 +17,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@@ -66,52 +67,61 @@
waitForUiOffloadThread();
verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
- eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean());
+ eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt());
}
@Test
public void testExpanded() throws RemoteException {
- mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+ mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+ NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
waitForUiOffloadThread();
verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
- eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean());
+ eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt());
}
@Test
public void testVisibleAndNotExpanded() throws RemoteException {
- mLogger.onExpansionChanged(NOTIFICATION_KEY, true, false);
+ mLogger.onExpansionChanged(NOTIFICATION_KEY, true, false,
+ NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
mLogger.onVisibilityChanged(
Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
Collections.emptyList());
waitForUiOffloadThread();
verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
- eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean());
+ eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt());
}
@Test
public void testVisibleAndExpanded() throws RemoteException {
- mLogger.onExpansionChanged(NOTIFICATION_KEY, true, true);
+ mLogger.onExpansionChanged(NOTIFICATION_KEY, true, true,
+ NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
mLogger.onVisibilityChanged(
Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
Collections.emptyList());
waitForUiOffloadThread();
verify(mBarService).onNotificationExpansionChanged(
- NOTIFICATION_KEY, true, true);
+ NOTIFICATION_KEY, true, true,
+ NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN.toMetricsEventEnum());
}
@Test
public void testExpandedAndVisible_expandedBeforeVisible() throws RemoteException {
- mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+ mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+ NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
mLogger.onVisibilityChanged(
- Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+ Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true,
+ NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA)),
Collections.emptyList());
waitForUiOffloadThread();
verify(mBarService).onNotificationExpansionChanged(
- NOTIFICATION_KEY, false, true);
+ NOTIFICATION_KEY, false, true,
+ // The last location seen should be logged (the one passed to onVisibilityChanged).
+ NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.toMetricsEventEnum()
+ );
}
@Test
@@ -119,11 +129,14 @@
mLogger.onVisibilityChanged(
Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
Collections.emptyList());
- mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+ mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+ NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
waitForUiOffloadThread();
verify(mBarService).onNotificationExpansionChanged(
- NOTIFICATION_KEY, false, true);
+ NOTIFICATION_KEY, false, true,
+ // The last location seen should be logged (the one passed to onExpansionChanged).
+ NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP.toMetricsEventEnum());
}
@Test
@@ -131,15 +144,24 @@
mLogger.onVisibilityChanged(
Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
Collections.emptyList());
- mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
- mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+ mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+ NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
+ mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+ NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
waitForUiOffloadThread();
verify(mBarService).onNotificationExpansionChanged(
- NOTIFICATION_KEY, false, true);
+ NOTIFICATION_KEY, false, true,
+ NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN.toMetricsEventEnum());
}
private NotificationVisibility createNotificationVisibility(String key, boolean visibility) {
- return NotificationVisibility.obtain(key, 0, 0, visibility);
+ return createNotificationVisibility(key, visibility,
+ NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
+ }
+
+ private NotificationVisibility createNotificationVisibility(String key, boolean visibility,
+ NotificationVisibility.NotificationLocation location) {
+ return NotificationVisibility.obtain(key, 0, 0, visibility, location);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 0899c73..40d2da9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -45,6 +45,7 @@
import android.app.NotificationChannel;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.metrics.LogMaker;
import android.os.Binder;
import android.os.Handler;
import android.provider.Settings;
@@ -55,6 +56,8 @@
import android.util.ArraySet;
import android.view.View;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -92,6 +95,7 @@
private NotificationGutsManager mGutsManager;
@Rule public MockitoRule mockito = MockitoJUnit.rule();
+ @Mock private MetricsLogger mMetricsLogger;
@Mock private NotificationPresenter mPresenter;
@Mock private NotificationActivityStarter mNotificationActivityStarter;
@Mock private NotificationStackScrollLayout mStackScroller;
@@ -105,6 +109,7 @@
Assert.sMainLooper = TestableLooper.get(this).getLooper();
mDependency.injectTestDependency(DeviceProvisionedController.class,
mDeviceProvisionedController);
+ mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mHandler = Handler.createAsync(mTestableLooper.getLooper());
mHelper = new NotificationTestHelper(mContext);
@@ -141,7 +146,7 @@
when(row.getWindowToken()).thenReturn(new Binder());
when(row.getGuts()).thenReturn(guts);
- mGutsManager.openGuts(row, 0, 0, menuItem);
+ assertTrue(mGutsManager.openGuts(row, 0, 0, menuItem));
assertEquals(View.INVISIBLE, guts.getVisibility());
mTestableLooper.processAllMessages();
verify(guts).openControls(
@@ -189,7 +194,7 @@
when(entry.getRow()).thenReturn(row);
when(entry.getGuts()).thenReturn(guts);
- mGutsManager.openGuts(row, 0, 0, menuItem);
+ assertTrue(mGutsManager.openGuts(row, 0, 0, menuItem));
mTestableLooper.processAllMessages();
verify(guts).openControls(
eq(true),
@@ -215,6 +220,34 @@
}
@Test
+ public void testOpenGutsLogging() {
+ NotificationGutsManager gutsManager = spy(mGutsManager);
+ doReturn(true).when(gutsManager).bindGuts(any(), any());
+
+ NotificationGuts guts = spy(new NotificationGuts(mContext));
+ doReturn(true).when(guts).post(any());
+
+ ExpandableNotificationRow realRow = createTestNotificationRow();
+ NotificationMenuRowPlugin.MenuItem menuItem = createTestMenuItem(realRow);
+
+ ExpandableNotificationRow row = spy(realRow);
+ when(row.getWindowToken()).thenReturn(new Binder());
+ when(row.getGuts()).thenReturn(guts);
+ StatusBarNotification notification = spy(realRow.getStatusBarNotification());
+ when(row.getStatusBarNotification()).thenReturn(notification);
+
+ assertTrue(gutsManager.openGuts(row, 0, 0, menuItem));
+
+ ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
+ verify(notification).getLogMaker();
+ verify(mMetricsLogger).write(logMakerCaptor.capture());
+ assertEquals(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS,
+ logMakerCaptor.getValue().getCategory());
+ assertEquals(MetricsProto.MetricsEvent.TYPE_ACTION,
+ logMakerCaptor.getValue().getType());
+ }
+
+ @Test
public void testAppOpsSettingsIntent_camera() {
ArraySet<Integer> ops = new ArraySet<>();
ops.add(OP_CAMERA);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
index 667a508..4dee438 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
@@ -17,7 +17,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -171,6 +170,19 @@
ev.recycle();
}
+ @Test
+ public void testViewNotAttachedNoCrash() {
+ View view = mockViewAt(0, 20, 10, 10);
+ when(view.isAttachedToWindow()).thenReturn(false);
+ mNearestTouchFrame.addView(view);
+ mNearestTouchFrame.onMeasure(0, 0);
+
+ MotionEvent ev = MotionEvent.obtain(0, 0, 0, 5 /* x */, 18 /* y */, 0);
+ mNearestTouchFrame.onTouchEvent(ev);
+ verify(view, never()).onTouchEvent(eq(ev));
+ ev.recycle();
+ }
+
private View mockViewAt(int x, int y, int width, int height) {
View v = spy(new View(mContext));
doAnswer(invocation -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
index 98d0c6b..9996a9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
@@ -95,4 +95,11 @@
public void testAdd_updatesVisibilityFlags() {
verify(mStatusBarView).setSystemUiVisibility(anyInt());
}
+
+ @Test
+ public void testSetForcePluginOpen_beforeStatusBarInitialization() {
+ mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
+ mActivityManager, mDozeParameters);
+ mStatusBarWindowController.setForcePluginOpen(true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index 3cbf902..03b7c95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -55,6 +55,9 @@
resources.addOverride(
R.bool.config_smart_replies_in_notifications_edit_choices_before_sending, false);
resources.addOverride(R.bool.config_smart_replies_in_notifications_show_in_heads_up, true);
+ resources.addOverride(
+ R.integer.config_smart_replies_in_notifications_min_num_system_generated_replies,
+ 2);
mConstants = new SmartReplyConstants(Handler.createAsync(Looper.myLooper()), mContext);
}
@@ -178,6 +181,19 @@
Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS, flags);
}
+ @Test
+ public void testGetMinNumSystemGeneratedRepliesWithNoConfig() {
+ assertTrue(mConstants.isEnabled());
+ assertEquals(2, mConstants.getMinNumSystemGeneratedReplies());
+ }
+
+ @Test
+ public void testGetMinNumSystemGeneratedRepliesWithValidConfig() {
+ overrideSetting("enabled=true,min_num_system_generated_replies=5");
+ triggerConstantsOnChange();
+ assertEquals(5, mConstants.getMinNumSystemGeneratedReplies());
+ }
+
private void triggerConstantsOnChange() {
// Since Settings.Global is mocked in TestableContext, we need to manually trigger the
// content observer.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 1066bc1..d1c4d01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -96,6 +96,7 @@
@Mock private SmartReplyController mLogger;
private NotificationEntry mEntry;
private Notification mNotification;
+ @Mock private SmartReplyConstants mConstants;
@Mock ActivityStarter mActivityStarter;
@Mock HeadsUpManager mHeadsUpManager;
@@ -108,10 +109,14 @@
mDependency.get(KeyguardDismissUtil.class).setDismissHandler(action -> action.onDismiss());
mDependency.injectMockDependency(ShadeController.class);
mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
+ mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
mContainer = new View(mContext, null);
mView = SmartReplyView.inflate(mContext, null);
+ // Any number of replies are fine.
+ when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0);
+ when(mConstants.getMaxSqueezeRemeasureAttempts()).thenReturn(3);
final Resources res = mContext.getResources();
mSingleLinePaddingHorizontal = res.getDimensionPixelSize(
@@ -403,7 +408,7 @@
}
private void setSmartReplies(CharSequence[] choices) {
- setSmartReplies(choices, false);
+ setSmartReplies(choices, false /* fromAssistant */);
}
private void setSmartReplies(CharSequence[] choices, boolean fromAssistant) {
@@ -440,9 +445,14 @@
}
private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) {
- setSmartReplies(choices);
+ setSmartRepliesAndActions(choices, actionTitles, false /* fromAssistant */);
+ }
+
+ private void setSmartRepliesAndActions(
+ CharSequence[] choices, String[] actionTitles, boolean fromAssistant) {
+ setSmartReplies(choices, fromAssistant);
mView.addSmartActions(
- new SmartReplyView.SmartActions(createActions(actionTitles), false),
+ new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant),
mLogger,
mEntry,
mHeadsUpManager);
@@ -943,4 +953,78 @@
expectedView.getChildAt(3), mView.getChildAt(4)); // a1
assertReplyButtonHidden(mView.getChildAt(5)); // long action
}
+
+ @Test
+ public void testMeasure_minNumSystemGeneratedSmartReplies_notEnoughReplies() {
+ when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(3);
+
+ // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
+ String[] choices = new String[] {"reply1", "reply2"};
+ String[] actions = new String[] {"action1"};
+
+ ViewGroup expectedView = buildExpectedView(new String[] {}, 1,
+ createActions(new String[] {"action1"}));
+ expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
+ mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ assertEqualMeasures(expectedView, mView);
+ // smart replies
+ assertReplyButtonHidden(mView.getChildAt(0));
+ assertReplyButtonHidden(mView.getChildAt(1));
+ // smart actions
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(2));
+ }
+
+ @Test
+ public void testMeasure_minNumSystemGeneratedSmartReplies_enoughReplies() {
+ when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2);
+
+ // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
+ String[] choices = new String[] {"reply1", "reply2"};
+ String[] actions = new String[] {"action1"};
+
+ ViewGroup expectedView = buildExpectedView(new String[] {"reply1", "reply2"}, 1,
+ createActions(new String[] {"action1"}));
+ expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
+ mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ assertEqualMeasures(expectedView, mView);
+ // smart replies
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
+ // smart actions
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
+ }
+
+ /**
+ * Ensure actions that are squeezed when shown together with smart replies are unsqueezed if the
+ * replies are never added (because of the SmartReplyConstants.getMinNumSystemGeneratedReplies()
+ * flag).
+ */
+ @Test
+ public void testMeasure_minNumSystemGeneratedSmartReplies_unSqueezeActions() {
+ when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2);
+
+ // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
+ String[] choices = new String[] {"This is a very long two-line reply."};
+ String[] actions = new String[] {"Short action"};
+
+ // The action should be displayed on one line only - since it fits!
+ ViewGroup expectedView = buildExpectedView(new String[] {}, 1 /* lineCount */,
+ createActions(new String[] {"Short action"}));
+ expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
+ mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ assertEqualMeasures(expectedView, mView);
+ // smart replies
+ assertReplyButtonHidden(mView.getChildAt(0));
+ // smart actions
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(1));
+ }
}
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 8261fe8..d1aa84f 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -73,6 +73,10 @@
// The view switched to summary mode (most relevant for notifications)
TYPE_COLLAPSE = 14;
+
+ // The notification was adjusted by the assistant. Enum value is
+ // out of sequence due to b/122737498.
+ TYPE_NOTIFICATION_ASSISTANT_ADJUSTMENT = 1573;
}
// Types of alerts, as bit field values
@@ -232,6 +236,17 @@
BLOCKING_HELPER_CLICK_UNDO = 7;
}
+ // The (visual) location of a Notification.
+ enum NotificationLocation {
+ LOCATION_UNKNOWN = 0;
+ LOCATION_FIRST_HEADS_UP = 1; // visible heads-up
+ LOCATION_HIDDEN_TOP = 2; // hidden/scrolled away on the top
+ LOCATION_MAIN_AREA = 3; // visible in the shade
+ LOCATION_BOTTOM_STACK_PEEKING = 4; // in the bottom stack, and peeking
+ LOCATION_BOTTOM_STACK_HIDDEN = 5; // in the bottom stack, and hidden
+ LOCATION_GONE = 6; // the view isn't laid out at all
+ }
+
// Known visual elements: views or controls.
enum View {
// Unknown view
@@ -4049,6 +4064,8 @@
// - AUTOFILL_INVALID_DATASET_AUTHENTICATION
// NOTE: starting on OS Q, it also added the following fields:
// Tag FIELD_AUTOFILL_TEXT_LEN: length of the error message provided by the service
+ // Tag FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS: number of requests made to the augmented
+ // autofill service
AUTOFILL_REQUEST = 907;
// Tag of a field for a package of an autofill service
@@ -6820,6 +6837,39 @@
// OS: Q
MOBILE_NETWORK_LIST = 1627;
+ // OPEN: Settings > Display > Adaptive sleep
+ // OS: Q
+ SETTINGS_ADAPTIVE_SLEEP = 1628;
+
+ // Tagged data for SMART_REPLY_VISIBLE and NOTIFICATION_ITEM_ACTION.
+ // The UI location of the notification containing the smart suggestions.
+ // This is a NotificationLocation object (see the NotificationLocation
+ // enum).
+ // OS: Q
+ NOTIFICATION_LOCATION = 1629;
+
+ // The autofill system made request to the system-provided augmented autofill service.
+ // OS: Q
+ // Package: Package of app that is autofilled
+ // Tag FIELD_CLASS_NAME: Class name of the activity that is autofilled.
+ // Tag FIELD_AUTOFILL_SERVICE: Package of the augmented autofill service that processed the
+ // request
+ // Tag FIELD_AUTOFILL_SESSION_ID: id of the autofill session associated with this metric.
+ // Tag FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
+ AUTOFILL_AUGMENTED_REQUEST = 1630;
+
+ // Tag of a field for the number of augmented autofill requests in a session
+ // OS: Q
+ FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS = 1631;
+
+ // OPEN: Settings > System > Aware
+ // OS: Q
+ SETTINGS_AWARE = 1632;
+
+ // OPEN: Settings > System > Aware > Disable > Dialog
+ // OS: Q
+ DIALOG_AWARE_DISABLE = 1633;
+
// ---- End Q Constants, all Q constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 1212676..3a89316 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -216,9 +216,15 @@
// Package: android
NOTE_SOFTAP_CONFIG_CHANGED = 50;
- // Notify the user that connected to app suggested network.
+ // Notify the user that an app suggested network is available for connection.
// Package: android
- NOTE_CONNECTED_TO_NETWORK_SUGGESTION = 51;
+ NOTE_NETWORK_SUGGESTION_AVAILABLE = 51;
+
+ // Inform the user that the contaminant is detected on the USB port
+ NOTE_USB_CONTAMINANT_DETECTED = 52;
+
+ // Inform that user that the USB port is free of contaminants.
+ NOTE_USB_CONTAMINANT_NOT_DETECTED = 53;
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
@@ -235,6 +241,8 @@
NOTE_NETWORK_LOST_INTERNET = 742;
// The system default network switched to a different network
NOTE_NETWORK_SWITCH = 743;
+ // Device logged-in captive portal network successfully
+ NOTE_NETWORK_LOGGED_IN = 744;
// Notify the user that their work profile has been deleted
// Package: android
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index bcc43a7..12f666e 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -488,6 +488,12 @@
// Counts the occurrences of each Wifi usability score provided by external app
repeated WifiUsabilityScoreCount wifi_usability_score_count = 127;
+
+ // List of PNO scan stats, one element for each mobility state
+ repeated DeviceMobilityStatePnoScanStats mobility_state_pno_stats_list = 128;
+
+ // Wifi p2p statistics
+ optional WifiP2pStats wifi_p2p_stats = 129;
}
// Information that gets logged for every WiFi connection.
@@ -1797,6 +1803,17 @@
// Sequence number from external system app to framework
optional int32 seq_num_to_framework = 19;
+
+ // The total time CCA is on busy status on the current frequency in ms
+ // counted from the last radio chip reset
+ optional int64 total_cca_busy_freq_time_ms = 20;
+
+ // The total radio on time of the current frequency from the last radio
+ // chip reset
+ optional int64 total_radio_on_freq_time_ms = 21;
+
+ // The total number of beacons received from the last radio chip reset
+ optional int64 total_beacon_rx = 22;
}
message WifiUsabilityStats {
@@ -1816,4 +1833,165 @@
// The list of timestamped wifi usability stats
repeated WifiUsabilityStatsEntry stats = 2;
-}
\ No newline at end of file
+}
+
+message DeviceMobilityStatePnoScanStats {
+ // see WifiManager.DEVICE_MOBILITY_STATE_* constants
+ enum DeviceMobilityState {
+ // Unknown mobility
+ UNKNOWN = 0;
+
+ // High movement
+ HIGH_MVMT = 1;
+
+ // Low movement
+ LOW_MVMT = 2;
+
+ // Stationary
+ STATIONARY = 3;
+ }
+
+ // The device mobility state
+ optional DeviceMobilityState device_mobility_state = 1;
+
+ // The number of times that this state was entered
+ optional int32 num_times_entered_state = 2;
+
+ // The total duration elapsed while in this mobility state, in ms
+ optional int64 total_duration_ms = 3;
+
+ // the total duration elapsed while in this mobility state with PNO scans running, in ms
+ optional int64 pno_duration_ms = 4;
+}
+
+// The information about the Wifi P2p events.
+message WifiP2pStats {
+
+ // Group event list tracking sessions and client counts in tethered mode.
+ repeated GroupEvent group_event = 1;
+
+ // Session information that gets logged for every Wifi P2p connection.
+ repeated P2pConnectionEvent connection_event = 2;
+
+ // Number of persistent group in the user profile.
+ optional int32 num_persistent_group = 3;
+
+ // Number of peer scan.
+ optional int32 num_total_peer_scans = 4;
+
+ // Number of service scan.
+ optional int32 num_total_service_scans = 5;
+}
+
+message P2pConnectionEvent {
+
+ enum ConnectionType {
+
+ // fresh new connection.
+ CONNECTION_FRESH = 0;
+
+ // reinvoke a group.
+ CONNECTION_REINVOKE = 1;
+
+ // create a group with the current device as the group owner locally.
+ CONNECTION_LOCAL = 2;
+
+ // create a group or join a group with config.
+ CONNECTION_FAST = 3;
+ }
+
+ enum ConnectivityLevelFailure {
+
+ // Failure is unknown.
+ CLF_UNKNOWN = 0;
+
+ // No failure.
+ CLF_NONE = 1;
+
+ // Timeout for current connecting request.
+ CLF_TIMEOUT = 2;
+
+ // The connecting request is canceled by the user.
+ CLF_CANCEL = 3;
+
+ // Provision discovery failure, e.g. no pin code, timeout, rejected by the peer.
+ CLF_PROV_DISC_FAIL = 4;
+
+ // Invitation failure, e.g. rejected by the peer.
+ CLF_INVITATION_FAIL = 5;
+
+ // Incoming request is rejected by the user.
+ CLF_USER_REJECT = 6;
+
+ // New connection request is issued before ending previous connecting request.
+ CLF_NEW_CONNECTION_ATTEMPT = 7;
+ }
+
+ // WPS method.
+ enum WpsMethod {
+ // WPS is skipped for Group Reinvoke.
+ WPS_NA = -1;
+
+ // Push button configuration.
+ WPS_PBC = 0;
+
+ // Display pin method configuration - pin is generated and displayed on device.
+ WPS_DISPLAY = 1;
+
+ // Keypad pin method configuration - pin is entered on device.
+ WPS_KEYPAD = 2;
+
+ // Label pin method configuration - pin is labelled on device.
+ WPS_LABEL = 3;
+ }
+
+ // Start time of the connection.
+ optional int64 start_time_millis = 1;
+
+ // Type of the connection.
+ optional ConnectionType connection_type = 2;
+
+ // WPS method.
+ optional WpsMethod wps_method = 3 [default = WPS_NA];
+
+ // Duration to connect.
+ optional int32 duration_taken_to_connect_millis = 4;
+
+ // Failures that happen at the connectivity layer.
+ optional ConnectivityLevelFailure connectivity_level_failure_code = 5;
+}
+
+// GroupEvent tracking group information from GroupStarted to GroupRemoved.
+message GroupEvent {
+
+ enum GroupRole {
+
+ GROUP_OWNER = 0;
+
+ GROUP_CLIENT = 1;
+ }
+
+ // The ID of network in supplicant for this group.
+ optional int32 net_id = 1;
+
+ // Start time of the group.
+ optional int64 start_time_millis = 2;
+
+ // Channel frequency used for Group.
+ optional int32 channel_frequency = 3;
+
+ // Is group owner or group client.
+ optional GroupRole group_role = 5;
+
+ // Number of connected clients.
+ optional int32 num_connected_clients = 6;
+
+ // Cumulative number of connected clients.
+ optional int32 num_cumulative_clients = 7;
+
+ // The session duration.
+ optional int32 session_duration_millis = 8;
+
+ // The idle duration. A group without any client is in idle.
+ optional int32 idle_duration_millis = 9;
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 6eba914..2c075dc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -53,6 +53,7 @@
import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.server.accessibility.AccessibilityManagerService.RemoteAccessibilityConnection;
@@ -751,7 +752,7 @@
}
@Override
- public float getMagnificationScale() {
+ public float getMagnificationScale(int displayId) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return 1.0f;
@@ -759,14 +760,14 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- return mSystemSupport.getMagnificationController().getScale();
+ return mSystemSupport.getMagnificationController().getScale(displayId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
- public Region getMagnificationRegion() {
+ public Region getMagnificationRegion(int displayId) {
synchronized (mLock) {
final Region region = Region.obtain();
if (!isCalledForCurrentUserLocked()) {
@@ -775,22 +776,22 @@
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
boolean registeredJustForThisCall =
- registerMagnificationIfNeeded(magnificationController);
+ registerMagnificationIfNeeded(displayId, magnificationController);
final long identity = Binder.clearCallingIdentity();
try {
- magnificationController.getMagnificationRegion(region);
+ magnificationController.getMagnificationRegion(displayId, region);
return region;
} finally {
Binder.restoreCallingIdentity(identity);
if (registeredJustForThisCall) {
- magnificationController.unregister();
+ magnificationController.unregister(displayId);
}
}
}
}
@Override
- public float getMagnificationCenterX() {
+ public float getMagnificationCenterX(int displayId) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return 0.0f;
@@ -798,21 +799,21 @@
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
boolean registeredJustForThisCall =
- registerMagnificationIfNeeded(magnificationController);
+ registerMagnificationIfNeeded(displayId, magnificationController);
final long identity = Binder.clearCallingIdentity();
try {
- return magnificationController.getCenterX();
+ return magnificationController.getCenterX(displayId);
} finally {
Binder.restoreCallingIdentity(identity);
if (registeredJustForThisCall) {
- magnificationController.unregister();
+ magnificationController.unregister(displayId);
}
}
}
}
@Override
- public float getMagnificationCenterY() {
+ public float getMagnificationCenterY(int displayId) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return 0.0f;
@@ -820,31 +821,31 @@
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
boolean registeredJustForThisCall =
- registerMagnificationIfNeeded(magnificationController);
+ registerMagnificationIfNeeded(displayId, magnificationController);
final long identity = Binder.clearCallingIdentity();
try {
- return magnificationController.getCenterY();
+ return magnificationController.getCenterY(displayId);
} finally {
Binder.restoreCallingIdentity(identity);
if (registeredJustForThisCall) {
- magnificationController.unregister();
+ magnificationController.unregister(displayId);
}
}
}
}
- private boolean registerMagnificationIfNeeded(
+ private boolean registerMagnificationIfNeeded(int displayId,
MagnificationController magnificationController) {
- if (!magnificationController.isRegisteredLocked()
+ if (!magnificationController.isRegistered(displayId)
&& mSecurityPolicy.canControlMagnification(this)) {
- magnificationController.register();
+ magnificationController.register(displayId);
return true;
}
return false;
}
@Override
- public boolean resetMagnification(boolean animate) {
+ public boolean resetMagnification(int displayId, boolean animate) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return false;
@@ -857,16 +858,16 @@
try {
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
- return (magnificationController.reset(animate)
- || !magnificationController.isMagnifying());
+ return (magnificationController.reset(displayId, animate)
+ || !magnificationController.isMagnifying(displayId));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
- public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
- boolean animate) {
+ public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX,
+ float centerY, boolean animate) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return false;
@@ -878,11 +879,11 @@
try {
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
- if (!magnificationController.isRegisteredLocked()) {
- magnificationController.register();
+ if (!magnificationController.isRegistered(displayId)) {
+ magnificationController.register(displayId);
}
return magnificationController
- .setScaleAndCenter(scale, centerX, centerY, animate, mId);
+ .setScaleAndCenter(displayId, scale, centerX, centerY, animate, mId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -890,12 +891,12 @@
}
@Override
- public void setMagnificationCallbackEnabled(boolean enabled) {
- mInvocationHandler.setMagnificationCallbackEnabled(enabled);
+ public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {
+ mInvocationHandler.setMagnificationCallbackEnabled(displayId, enabled);
}
- public boolean isMagnificationCallbackEnabled() {
- return mInvocationHandler.mIsMagnificationCallbackEnabled;
+ public boolean isMagnificationCallbackEnabled(int displayId) {
+ return mInvocationHandler.isMagnificationCallbackEnabled(displayId);
}
@Override
@@ -1106,10 +1107,10 @@
InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
}
- public void notifyMagnificationChangedLocked(@NonNull Region region,
+ public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
mInvocationHandler
- .notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+ .notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY);
}
public void notifySoftKeyboardShowModeChangedLocked(int showState) {
@@ -1128,12 +1129,12 @@
* Called by the invocation handler to notify the service that the
* state of magnification has changed.
*/
- private void notifyMagnificationChangedInternal(@NonNull Region region,
+ private void notifyMagnificationChangedInternal(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
if (listener != null) {
try {
- listener.onMagnificationChanged(region, scale, centerX, centerY);
+ listener.onMagnificationChanged(displayId, region, scale, centerX, centerY);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
}
@@ -1251,7 +1252,9 @@
private static final int MSG_ON_ACCESSIBILITY_BUTTON_CLICKED = 7;
private static final int MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 8;
- private boolean mIsMagnificationCallbackEnabled = false;
+ /** List of magnification callback states, mapping from displayId -> Boolean */
+ @GuardedBy("mlock")
+ private final SparseArray<Boolean> mMagnificationCallbackState = new SparseArray<>(0);
private boolean mIsSoftKeyboardCallbackEnabled = false;
public InvocationHandler(Looper looper) {
@@ -1277,7 +1280,8 @@
final float scale = (float) args.arg2;
final float centerX = (float) args.arg3;
final float centerY = (float) args.arg4;
- notifyMagnificationChangedInternal(region, scale, centerX, centerY);
+ final int displayId = args.argi1;
+ notifyMagnificationChangedInternal(displayId, region, scale, centerX, centerY);
args.recycle();
} break;
@@ -1301,11 +1305,12 @@
}
}
- public void notifyMagnificationChangedLocked(@NonNull Region region, float scale,
- float centerX, float centerY) {
- if (!mIsMagnificationCallbackEnabled) {
- // Callback is disabled, don't bother packing args.
- return;
+ public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
+ float scale, float centerX, float centerY) {
+ synchronized (mLock) {
+ if (mMagnificationCallbackState.get(displayId) == null) {
+ return;
+ }
}
final SomeArgs args = SomeArgs.obtain();
@@ -1313,13 +1318,26 @@
args.arg2 = scale;
args.arg3 = centerX;
args.arg4 = centerY;
+ args.argi1 = displayId;
final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args);
msg.sendToTarget();
}
- public void setMagnificationCallbackEnabled(boolean enabled) {
- mIsMagnificationCallbackEnabled = enabled;
+ public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {
+ synchronized (mLock) {
+ if (enabled) {
+ mMagnificationCallbackState.put(displayId, true);
+ } else {
+ mMagnificationCallbackState.remove(displayId);
+ }
+ }
+ }
+
+ public boolean isMagnificationCallbackEnabled(int displayId) {
+ synchronized (mLock) {
+ return mMagnificationCallbackState.get(displayId) != null;
+ }
}
public void notifySoftKeyboardShowModeChangedLocked(int showState) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index cf08681..bcff4e0 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -17,13 +17,11 @@
package com.android.server.accessibility;
import android.content.Context;
-import android.os.Handler;
import android.os.PowerManager;
-import android.util.DebugUtils;
-import android.util.ExceptionUtils;
-import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.view.Display;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputFilter;
@@ -31,10 +29,11 @@
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityEvent;
-import com.android.internal.util.BitUtils;
import com.android.server.LocalServices;
import com.android.server.policy.WindowManagerPolicy;
+import java.util.ArrayList;
+
/**
* This class is an input filter for implementing accessibility features such
* as display magnification and explore by touch.
@@ -108,23 +107,24 @@
private final AccessibilityManagerService mAms;
- private boolean mInstalled;
+ private final SparseArray<EventStreamTransformation> mEventHandler;
- private int mUserId;
+ private final SparseArray<TouchExplorer> mTouchExplorer = new SparseArray<>(0);
- private int mEnabledFeatures;
+ private final SparseArray<MagnificationGestureHandler> mMagnificationGestureHandler =
+ new SparseArray<>(0);
- private TouchExplorer mTouchExplorer;
-
- private MagnificationGestureHandler mMagnificationGestureHandler;
-
- private MotionEventInjector mMotionEventInjector;
+ private final SparseArray<MotionEventInjector> mMotionEventInjector = new SparseArray<>(0);
private AutoclickController mAutoclickController;
private KeyboardInterceptor mKeyboardInterceptor;
- private EventStreamTransformation mEventHandler;
+ private boolean mInstalled;
+
+ private int mUserId;
+
+ private int mEnabledFeatures;
private EventStreamState mMouseStreamState;
@@ -133,10 +133,16 @@
private EventStreamState mKeyboardStreamState;
AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
+ this(context, service, new SparseArray<>(0));
+ }
+
+ AccessibilityInputFilter(Context context, AccessibilityManagerService service,
+ SparseArray<EventStreamTransformation> eventHandler) {
super(context.getMainLooper());
mContext = context;
mAms = service;
mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mEventHandler = eventHandler;
}
@Override
@@ -160,6 +166,13 @@
super.onUninstalled();
}
+ void onDisplayChanged() {
+ if (mInstalled) {
+ disableFeatures();
+ enableFeatures();
+ }
+ }
+
@Override
public void onInputEvent(InputEvent event, int policyFlags) {
if (DEBUG) {
@@ -167,8 +180,8 @@
+ Integer.toHexString(policyFlags));
}
- if (mEventHandler == null) {
- if (DEBUG) Slog.d(TAG, "mEventHandler == null for event " + event);
+ if (mEventHandler.size() == 0) {
+ if (DEBUG) Slog.d(TAG, "No mEventHandler for event " + event);
super.onInputEvent(event, policyFlags);
return;
}
@@ -182,16 +195,16 @@
int eventSource = event.getSource();
if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
state.reset();
- mEventHandler.clearEvents(eventSource);
+ clearEventsForAllEventHandlers(eventSource);
super.onInputEvent(event, policyFlags);
return;
}
- if (state.updateDeviceId(event.getDeviceId())) {
- mEventHandler.clearEvents(eventSource);
+ if (state.updateInputSource(event.getSource())) {
+ clearEventsForAllEventHandlers(eventSource);
}
- if (!state.deviceIdValid()) {
+ if (!state.inputSourceValid()) {
super.onInputEvent(event, policyFlags);
return;
}
@@ -240,6 +253,15 @@
return null;
}
+ private void clearEventsForAllEventHandlers(int eventSource) {
+ for (int i = 0; i < mEventHandler.size(); i++) {
+ final EventStreamTransformation eventHandler = mEventHandler.valueAt(i);
+ if (eventHandler != null) {
+ eventHandler.clearEvents(eventSource);
+ }
+ }
+ }
+
private void processMotionEvent(EventStreamState state, MotionEvent event, int policyFlags) {
if (!state.shouldProcessScroll() && event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
super.onInputEvent(event, policyFlags);
@@ -258,7 +280,10 @@
super.onInputEvent(event, policyFlags);
return;
}
- mEventHandler.onKeyEvent(event, policyFlags);
+ // Since the display id of KeyEvent always would be -1 and there is only one
+ // KeyboardInterceptor for all display, pass KeyEvent to the mEventHandler of
+ // DEFAULT_DISPLAY to handle.
+ mEventHandler.get(Display.DEFAULT_DISPLAY).onKeyEvent(event, policyFlags);
}
private void handleMotionEvent(MotionEvent event, int policyFlags) {
@@ -267,10 +292,16 @@
}
mPm.userActivity(event.getEventTime(), false);
MotionEvent transformedEvent = MotionEvent.obtain(event);
- mEventHandler.onMotionEvent(transformedEvent, event, policyFlags);
+ final int displayId = event.getDisplayId();
+ mEventHandler.get(isDisplayIdValid(displayId) ? displayId : Display.DEFAULT_DISPLAY)
+ .onMotionEvent(transformedEvent, event, policyFlags);
transformedEvent.recycle();
}
+ private boolean isDisplayIdValid(int displayId) {
+ return mEventHandler.get(displayId) != null;
+ }
+
@Override
public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent,
int policyFlags) {
@@ -323,14 +354,20 @@
}
void notifyAccessibilityEvent(AccessibilityEvent event) {
- if (mEventHandler != null) {
- mEventHandler.onAccessibilityEvent(event);
+ for (int i = 0; i < mEventHandler.size(); i++) {
+ final EventStreamTransformation eventHandler = mEventHandler.valueAt(i);
+ if (eventHandler != null) {
+ eventHandler.onAccessibilityEvent(event);
+ }
}
}
- void notifyAccessibilityButtonClicked() {
- if (mMagnificationGestureHandler != null) {
- mMagnificationGestureHandler.notifyShortcutTriggered();
+ void notifyAccessibilityButtonClicked(int displayId) {
+ if (mMagnificationGestureHandler.size() != 0) {
+ final MagnificationGestureHandler handler = mMagnificationGestureHandler.get(displayId);
+ if (handler != null) {
+ handler.notifyShortcutTriggered();
+ }
}
}
@@ -339,81 +376,124 @@
resetStreamState();
+ final ArrayList<Display> displaysList = mAms.getValidDisplayList();
+
if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
mAutoclickController = new AutoclickController(mContext, mUserId);
- addFirstEventHandler(mAutoclickController);
+ addFirstEventHandlerForAllDisplays(displaysList, mAutoclickController);
}
- if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
- mTouchExplorer = new TouchExplorer(mContext, mAms);
- addFirstEventHandler(mTouchExplorer);
- }
+ for (int i = displaysList.size() - 1; i >= 0; i--) {
+ final int displayId = displaysList.get(i).getDisplayId();
- if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
- || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
- || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
- final boolean detectControlGestures = (mEnabledFeatures
- & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0;
- final boolean triggerable = (mEnabledFeatures
- & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0;
- mMagnificationGestureHandler = new MagnificationGestureHandler(
- mContext, mAms.getMagnificationController(),
- detectControlGestures, triggerable);
- addFirstEventHandler(mMagnificationGestureHandler);
- }
+ if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
+ TouchExplorer explorer = new TouchExplorer(mContext, mAms);
+ addFirstEventHandler(displayId, explorer);
+ mTouchExplorer.put(displayId, explorer);
+ }
- if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
- mMotionEventInjector = new MotionEventInjector(mContext.getMainLooper());
- addFirstEventHandler(mMotionEventInjector);
- mAms.setMotionEventInjector(mMotionEventInjector);
+ if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
+ || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
+ || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
+ final boolean detectControlGestures = (mEnabledFeatures
+ & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0;
+ final boolean triggerable = (mEnabledFeatures
+ & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0;
+ MagnificationGestureHandler magnificationGestureHandler =
+ new MagnificationGestureHandler(mContext,
+ mAms.getMagnificationController(),
+ detectControlGestures, triggerable);
+ addFirstEventHandler(displayId, magnificationGestureHandler);
+ mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
+ }
+
+ if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
+ MotionEventInjector injector = new MotionEventInjector(
+ mContext.getMainLooper());
+ addFirstEventHandler(displayId, injector);
+ // TODO: Need to set MotionEventInjector per display.
+ mAms.setMotionEventInjector(injector);
+ mMotionEventInjector.put(displayId, injector);
+ }
}
if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) {
mKeyboardInterceptor = new KeyboardInterceptor(mAms,
LocalServices.getService(WindowManagerPolicy.class));
- addFirstEventHandler(mKeyboardInterceptor);
+ // Since the display id of KeyEvent always would be -1 and it would be dispatched to
+ // the display with input focus directly, we only need one KeyboardInterceptor for
+ // default display.
+ addFirstEventHandler(Display.DEFAULT_DISPLAY, mKeyboardInterceptor);
}
}
/**
- * Adds an event handler to the event handler chain. The handler is added at the beginning of
- * the chain.
+ * Adds an event handler to the event handler chain for giving display. The handler is added at
+ * the beginning of the chain.
*
+ * @param displayId The logical display id.
* @param handler The handler to be added to the event handlers list.
*/
- private void addFirstEventHandler(EventStreamTransformation handler) {
- if (mEventHandler != null) {
- handler.setNext(mEventHandler);
+ private void addFirstEventHandler(int displayId, EventStreamTransformation handler) {
+ EventStreamTransformation eventHandler = mEventHandler.get(displayId);
+ if (eventHandler != null) {
+ handler.setNext(eventHandler);
} else {
handler.setNext(this);
}
- mEventHandler = handler;
+ eventHandler = handler;
+ mEventHandler.put(displayId, eventHandler);
+ }
+
+ /**
+ * Adds an event handler to the event handler chain for all displays. The handler is added at
+ * the beginning of the chain.
+ *
+ * @param displayList The list of displays
+ * @param handler The handler to be added to the event handlers list.
+ */
+ private void addFirstEventHandlerForAllDisplays(ArrayList<Display> displayList,
+ EventStreamTransformation handler) {
+ for (int i = 0; i < displayList.size(); i++) {
+ final int displayId = displayList.get(i).getDisplayId();
+ addFirstEventHandler(displayId, handler);
+ }
}
private void disableFeatures() {
- if (mMotionEventInjector != null) {
+ for (int i = mMotionEventInjector.size() - 1; i >= 0; i--) {
+ final MotionEventInjector injector = mMotionEventInjector.valueAt(i);
+ // TODO: Need to set MotionEventInjector per display.
mAms.setMotionEventInjector(null);
- mMotionEventInjector.onDestroy();
- mMotionEventInjector = null;
+ if (injector != null) {
+ injector.onDestroy();
+ }
}
+ mMotionEventInjector.clear();
if (mAutoclickController != null) {
mAutoclickController.onDestroy();
mAutoclickController = null;
}
- if (mTouchExplorer != null) {
- mTouchExplorer.onDestroy();
- mTouchExplorer = null;
+ for (int i = mTouchExplorer.size() - 1; i >= 0; i--) {
+ final TouchExplorer explorer = mTouchExplorer.valueAt(i);
+ if (explorer != null) {
+ explorer.onDestroy();
+ }
}
- if (mMagnificationGestureHandler != null) {
- mMagnificationGestureHandler.onDestroy();
- mMagnificationGestureHandler = null;
+ mTouchExplorer.clear();
+ for (int i = mMagnificationGestureHandler.size() - 1; i >= 0; i--) {
+ final MagnificationGestureHandler handler = mMagnificationGestureHandler.valueAt(i);
+ if (handler != null) {
+ handler.onDestroy();
+ }
}
+ mMagnificationGestureHandler.clear();
if (mKeyboardInterceptor != null) {
mKeyboardInterceptor.onDestroy();
mKeyboardInterceptor = null;
}
- mEventHandler = null;
+ mEventHandler.clear();
resetStreamState();
}
@@ -441,41 +521,41 @@
* whose events should not be handled by a11y event stream transformations.
*/
private static class EventStreamState {
- private int mDeviceId;
+ private int mSource;
EventStreamState() {
- mDeviceId = -1;
+ mSource = -1;
}
/**
- * Updates the ID of the device associated with the state. If the ID changes, resets
- * internal state.
+ * Updates the input source of the device associated with the state. If the source changes,
+ * resets internal state.
*
- * @param deviceId Updated input device ID.
- * @return Whether the device ID has changed.
+ * @param source Updated input source.
+ * @return Whether the input source has changed.
*/
- public boolean updateDeviceId(int deviceId) {
- if (mDeviceId == deviceId) {
+ public boolean updateInputSource(int source) {
+ if (mSource == source) {
return false;
}
- // Reset clears internal state, so make sure it's called before |mDeviceId| is updated.
+ // Reset clears internal state, so make sure it's called before |mSource| is updated.
reset();
- mDeviceId = deviceId;
+ mSource = source;
return true;
}
/**
- * @return Whether device ID is valid.
+ * @return Whether input source is valid.
*/
- public boolean deviceIdValid() {
- return mDeviceId >= 0;
+ public boolean inputSourceValid() {
+ return mSource >= 0;
}
/**
* Resets the event stream state.
*/
public void reset() {
- mDeviceId = -1;
+ mSource = -1;
}
/**
@@ -592,20 +672,19 @@
/*
* Key events from different devices may be interleaved. For example, the volume up and
- * down keys can come from different device IDs.
+ * down keys can come from different input sources.
*/
@Override
- public boolean updateDeviceId(int deviceId) {
+ public boolean updateInputSource(int deviceId) {
return false;
}
- // We manage all device ids simultaneously; there is no concept of validity.
+ // We manage all input source simultaneously; there is no concept of validity.
@Override
- public boolean deviceIdValid() {
+ public boolean inputSourceValid() {
return true;
}
-
@Override
final public boolean shouldProcessKeyEvent(KeyEvent event) {
// For each keyboard device, wait for a down event from a device to start processing
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 763c16f..305c53e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -38,6 +38,7 @@
import android.accessibilityservice.IAccessibilityServiceClient;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.app.AlertDialog;
import android.app.AppOpsManager;
import android.app.PendingIntent;
@@ -212,6 +213,8 @@
private final SecurityPolicy mSecurityPolicy;
+ private final AccessibilityDisplayListener mA11yDisplayListener;
+
private final AppOpsManager mAppOpsManager;
private final MainHandler mMainHandler;
@@ -304,6 +307,7 @@
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mMainHandler = new MainHandler(mContext.getMainLooper());
mGlobalActionPerformer = new GlobalActionPerformer(mContext, mWindowManagerService);
+ mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
registerBroadcastReceivers();
new AccessibilityContentObserver(mMainHandler).register(
@@ -909,16 +913,18 @@
/**
* Invoked remotely over AIDL by SysUi when the accessibility button within the system's
* navigation area has been clicked.
+ *
+ * @param displayId The logical display id.
*/
@Override
- public void notifyAccessibilityButtonClicked() {
+ public void notifyAccessibilityButtonClicked(int displayId) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Caller does not hold permission "
+ android.Manifest.permission.STATUS_BAR_SERVICE);
}
synchronized (mLock) {
- notifyAccessibilityButtonClickedLocked();
+ notifyAccessibilityButtonClickedLocked(displayId);
}
}
@@ -968,17 +974,18 @@
* Called by the MagnificationController when the state of display
* magnification changes.
*
+ * @param displayId The logical display id.
* @param region the new magnified region, may be empty if
* magnification is not enabled (e.g. scale is 1)
* @param scale the new scale
* @param centerX the new screen-relative center X coordinate
* @param centerY the new screen-relative center Y coordinate
*/
- public void notifyMagnificationChanged(@NonNull Region region,
+ public void notifyMagnificationChanged(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
synchronized (mLock) {
notifyClearAccessibilityCacheLocked();
- notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+ notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY);
}
}
@@ -1203,12 +1210,12 @@
}
}
- private void notifyMagnificationChangedLocked(@NonNull Region region,
+ private void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
final UserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = state.mBoundServices.get(i);
- service.notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+ service.notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY);
}
}
@@ -1220,7 +1227,7 @@
}
}
- private void notifyAccessibilityButtonClickedLocked() {
+ private void notifyAccessibilityButtonClickedLocked(int displayId) {
final UserState state = getCurrentUserStateLocked();
int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0;
@@ -1237,12 +1244,15 @@
if (potentialTargets == 1) {
if (state.mIsNavBarMagnificationEnabled) {
mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this));
+ AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
+ displayId));
return;
} else {
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mRequestAccessibilityButton) {
+ // TODO(b/120762691): Need to notify each accessibility service if
+ // accessibility button is clicked per display.
service.notifyAccessibilityButtonClickedLocked();
return;
}
@@ -1252,17 +1262,21 @@
if (state.mServiceAssignedToAccessibilityButton == null
&& !state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::showAccessibilityButtonTargetSelection, this));
+ AccessibilityManagerService::showAccessibilityButtonTargetSelection, this,
+ displayId));
} else if (state.mIsNavBarMagnificationEnabled
&& state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this));
+ AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
+ displayId));
return;
} else {
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
state.mServiceAssignedToAccessibilityButton))) {
+ // TODO(b/120762691): Need to notify each accessibility service if
+ // accessibility button is clicked per display.
service.notifyAccessibilityButtonClickedLocked();
return;
}
@@ -1270,22 +1284,24 @@
}
// The user may have turned off the assigned service or feature
mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::showAccessibilityButtonTargetSelection, this));
+ AccessibilityManagerService::showAccessibilityButtonTargetSelection, this,
+ displayId));
}
}
- private void sendAccessibilityButtonToInputFilter() {
+ private void sendAccessibilityButtonToInputFilter(int displayId) {
synchronized (mLock) {
if (mHasInputFilter && mInputFilter != null) {
- mInputFilter.notifyAccessibilityButtonClicked();
+ mInputFilter.notifyAccessibilityButtonClicked(displayId);
}
}
}
- private void showAccessibilityButtonTargetSelection() {
+ private void showAccessibilityButtonTargetSelection(int displayId) {
Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- mContext.startActivityAsUser(intent, UserHandle.of(mCurrentUserId));
+ final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
+ mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId));
}
private void notifyAccessibilityButtonVisibilityChangedLocked(boolean available) {
@@ -2191,15 +2207,34 @@
return;
}
- if (!mUiAutomationManager.suppressingAccessibilityServicesLocked()
- && (userState.mIsDisplayMagnificationEnabled
- || userState.mIsNavBarMagnificationEnabled
- || userHasListeningMagnificationServicesLocked(userState))) {
- // Initialize the magnification controller if necessary
- getMagnificationController();
- mMagnificationController.register();
- } else if (mMagnificationController != null) {
- mMagnificationController.unregister();
+ if (mUiAutomationManager.suppressingAccessibilityServicesLocked()
+ && mMagnificationController != null) {
+ mMagnificationController.unregisterAll();
+ return;
+ }
+
+ // Get all valid displays and register them if global magnification is enabled.
+ // We would skip overlay display because it uses overlay window to simulate secondary
+ // displays in one display. It's not a real display and there's no input events for it.
+ final ArrayList<Display> displays = getValidDisplayList();
+ if (userState.mIsDisplayMagnificationEnabled
+ || userState.mIsNavBarMagnificationEnabled) {
+ for (int i = 0; i < displays.size(); i++) {
+ final Display display = displays.get(i);
+ getMagnificationController().register(display.getDisplayId());
+ }
+ return;
+ }
+
+ // Register if display has listening magnification services.
+ for (int i = 0; i < displays.size(); i++) {
+ final Display display = displays.get(i);
+ final int displayId = display.getDisplayId();
+ if (userHasListeningMagnificationServicesLocked(userState, displayId)) {
+ getMagnificationController().register(displayId);
+ } else if (mMagnificationController != null) {
+ mMagnificationController.unregister(displayId);
+ }
}
}
@@ -2222,12 +2257,13 @@
* Returns whether the specified user has any services that are capable of
* controlling magnification and are actively listening for magnification updates.
*/
- private boolean userHasListeningMagnificationServicesLocked(UserState userState) {
+ private boolean userHasListeningMagnificationServicesLocked(UserState userState,
+ int displayId) {
final List<AccessibilityServiceConnection> services = userState.mBoundServices;
for (int i = 0, count = services.size(); i < count; i++) {
final AccessibilityServiceConnection service = services.get(i);
if (mSecurityPolicy.canControlMagnification(service)
- && service.isMagnificationCallbackEnabled()) {
+ && service.isMagnificationCallbackEnabled(displayId)) {
return true;
}
}
@@ -3753,6 +3789,92 @@
}
}
+ /**
+ * Gets all currently valid logical displays.
+ *
+ * @return An array list containing all valid logical displays.
+ */
+ public ArrayList<Display> getValidDisplayList() {
+ return mA11yDisplayListener.getValidDisplayList();
+ }
+
+ /**
+ * A Utility class to handle display state.
+ */
+ public class AccessibilityDisplayListener implements DisplayManager.DisplayListener {
+ private final DisplayManager mDisplayManager;
+ private final ArrayList<Display> mDisplaysList = new ArrayList<>();
+
+ AccessibilityDisplayListener(Context context, MainHandler handler) {
+ mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+ mDisplayManager.registerDisplayListener(this, handler);
+ initializeDisplayList();
+ }
+
+ ArrayList<Display> getValidDisplayList() {
+ synchronized (mLock) {
+ return mDisplaysList;
+ }
+ }
+
+ private void initializeDisplayList() {
+ final Display[] displays = mDisplayManager.getDisplays();
+ synchronized (mLock) {
+ mDisplaysList.clear();
+ for (int i = 0; i < displays.length; i++) {
+ // Exclude overlay virtual displays. The display list is for A11yInputFilter
+ // to create event handler per display. The events should be handled by the
+ // display which is overlaid by it.
+ final Display display = displays[i];
+ if (display.getType() == Display.TYPE_OVERLAY) {
+ continue;
+ }
+ mDisplaysList.add(display);
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ final Display display = mDisplayManager.getDisplay(displayId);
+ if (display == null || display.getType() == Display.TYPE_OVERLAY) {
+ return;
+ }
+
+ synchronized (mLock) {
+ mDisplaysList.add(display);
+ if (mInputFilter != null) {
+ mInputFilter.onDisplayChanged();
+ }
+ UserState userState = getCurrentUserStateLocked();
+ updateMagnificationLocked(userState);
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ synchronized (mLock) {
+ for (int i = 0; i < mDisplaysList.size(); i++) {
+ if (mDisplaysList.get(i).getDisplayId() == displayId) {
+ mDisplaysList.remove(i);
+ break;
+ }
+ }
+ if (mInputFilter != null) {
+ mInputFilter.onDisplayChanged();
+ }
+ }
+ if (mMagnificationController != null) {
+ mMagnificationController.onDisplayRemoved(displayId);
+ }
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ /* do nothing */
+ }
+ }
+
/** Represents an {@link AccessibilityManager} */
class Client {
final IAccessibilityManagerClient mCallback;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 86132a8..a19a847 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -109,7 +109,7 @@
UserState userState = mUserStateWeakReference.get();
if (userState == null) return;
userState.removeServiceLocked(this);
- mSystemSupport.getMagnificationController().resetIfNeeded(mId);
+ mSystemSupport.getMagnificationController().resetAllIfNeeded(mId);
resetLocked();
}
@@ -256,7 +256,7 @@
userState.serviceDisconnectedLocked(this);
}
resetLocked();
- mSystemSupport.getMagnificationController().resetIfNeeded(mId);
+ mSystemSupport.getMagnificationController().resetAllIfNeeded(mId);
mSystemSupport.onClientChangeLocked(false);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index 6a97fbb..e784056 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -19,7 +19,6 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -32,6 +31,7 @@
import android.text.TextUtils;
import android.util.MathUtils;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.Display;
import android.view.MagnificationSpec;
import android.view.View;
@@ -39,6 +39,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.wm.WindowManagerInternal;
@@ -68,9 +69,7 @@
private final Object mLock;
- private final AccessibilityManagerService mAms;
-
- private final SettingsBridge mSettingsBridge;
+ private final ControllerContext mControllerCtx;
private final ScreenStateObserver mScreenStateObserver;
@@ -78,11 +77,9 @@
private final long mMainThreadId;
- private Handler mHandler;
-
- private final WindowManagerInternal mWindowManager;
-
- private final DisplayMagnification mDisplay;
+ /** List of display Magnification, mapping from displayId -> DisplayMagnification. */
+ @GuardedBy("mLock")
+ private final SparseArray<DisplayMagnification> mDisplays = new SparseArray<>(0);
/**
* This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds
@@ -107,46 +104,82 @@
// Flag indicating that we are registered with window manager.
private boolean mRegistered;
private boolean mUnregisterPending;
+ private boolean mDeleteAfterUnregister;
private final int mDisplayId;
private static final int INVALID_ID = -1;
private int mIdOfLastServiceToMagnify = INVALID_ID;
-
- DisplayMagnification(int displayId, SpecAnimationBridge specAnimation) {
+ DisplayMagnification(int displayId) {
mDisplayId = displayId;
- mSpecAnimationBridge = specAnimation;
+ mSpecAnimationBridge = new SpecAnimationBridge(mControllerCtx, mLock, mDisplayId);
}
- void register() {
- synchronized (mLock) {
- if (!mRegistered) {
- mWindowManager.setMagnificationCallbacks(this);
- mSpecAnimationBridge.setEnabled(true);
- // Obtain initial state.
- mWindowManager.getMagnificationRegion(mMagnificationRegion);
- mMagnificationRegion.getBounds(mMagnificationBounds);
- mRegistered = true;
- }
+ /**
+ * Registers magnification callback and get current magnification region from
+ * window manager.
+ *
+ * @return true if callback registers successful.
+ */
+ @GuardedBy("mLock")
+ boolean register() {
+ mRegistered = mControllerCtx.getWindowManager().setMagnificationCallbacks(
+ mDisplayId, this);
+ if (!mRegistered) {
+ Slog.w(LOG_TAG, "set magnification callbacks fail, displayId:" + mDisplayId);
+ return false;
}
+ mSpecAnimationBridge.setEnabled(true);
+ // Obtain initial state.
+ mControllerCtx.getWindowManager().getMagnificationRegion(
+ mDisplayId, mMagnificationRegion);
+ mMagnificationRegion.getBounds(mMagnificationBounds);
+ return true;
}
- void unregister() {
- synchronized (mLock) {
- if (!isMagnifying()) {
- unregisterInternalLocked();
- } else {
- mUnregisterPending = true;
- reset(true);
- }
+ /**
+ * Unregisters magnification callback from window manager. Callbacks to
+ * {@link MagnificationController#unregisterCallbackLocked(int, boolean)} after
+ * unregistered.
+ *
+ * @param delete true if this instance should be removed from the SparseArray in
+ * MagnificationController after unregistered, for example, display removed.
+ */
+ @GuardedBy("mLock")
+ void unregister(boolean delete) {
+ if (mRegistered) {
+ mSpecAnimationBridge.setEnabled(false);
+ mControllerCtx.getWindowManager().setMagnificationCallbacks(
+ mDisplayId, null);
+ mMagnificationRegion.setEmpty();
+ mRegistered = false;
+ unregisterCallbackLocked(mDisplayId, delete);
}
+ mUnregisterPending = false;
}
- boolean isRegisteredLocked() {
+ /**
+ * Reset magnification status with animation enabled. {@link #unregister(boolean)} will be
+ * called after animation finished.
+ *
+ * @param delete true if this instance should be removed from the SparseArray in
+ * MagnificationController after unregistered, for example, display removed.
+ */
+ @GuardedBy("mLock")
+ void unregisterPending(boolean delete) {
+ mDeleteAfterUnregister = delete;
+ mUnregisterPending = true;
+ reset(true);
+ }
+
+ boolean isRegistered() {
return mRegistered;
}
+ boolean isMagnifying() {
+ return mCurrentMagnificationSpec.scale > 1.0f;
+ }
float getScale() {
return mCurrentMagnificationSpec.scale;
@@ -156,18 +189,20 @@
return mCurrentMagnificationSpec.offsetX;
}
- float getCenterX() {
- synchronized (mLock) {
- return (mMagnificationBounds.width() / 2.0f
- + mMagnificationBounds.left - getOffsetX()) / getScale();
- }
+ float getOffsetY() {
+ return mCurrentMagnificationSpec.offsetY;
}
+ @GuardedBy("mLock")
+ float getCenterX() {
+ return (mMagnificationBounds.width() / 2.0f
+ + mMagnificationBounds.left - getOffsetX()) / getScale();
+ }
+
+ @GuardedBy("mLock")
float getCenterY() {
- synchronized (mLock) {
- return (mMagnificationBounds.height() / 2.0f
- + mMagnificationBounds.top - getOffsetY()) / getScale();
- }
+ return (mMagnificationBounds.height() / 2.0f
+ + mMagnificationBounds.top - getOffsetY()) / getScale();
}
/**
@@ -203,64 +238,35 @@
return mSpecAnimationBridge.mSentMagnificationSpec.offsetY;
}
- boolean resetIfNeeded(boolean animate) {
- synchronized (mLock) {
- if (isMagnifying()) {
- reset(animate);
- return true;
- }
- return false;
- }
- }
-
- float getOffsetY() {
- return mCurrentMagnificationSpec.offsetY;
- }
-
- boolean isMagnifying() {
- return mCurrentMagnificationSpec.scale > 1.0f;
- }
-
- void unregisterInternalLocked() {
- if (mRegistered) {
- mSpecAnimationBridge.setEnabled(false);
- mWindowManager.setMagnificationCallbacks(null);
- mMagnificationRegion.setEmpty();
-
- mRegistered = false;
- }
- mUnregisterPending = false;
- }
-
-
@Override
public void onMagnificationRegionChanged(Region magnificationRegion) {
final Message m = PooledLambda.obtainMessage(
- DisplayMagnification.this::updateMagnificationRegion,
+ DisplayMagnification::updateMagnificationRegion, this,
Region.obtain(magnificationRegion));
- mHandler.sendMessage(m);
+ mControllerCtx.getHandler().sendMessage(m);
}
@Override
public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
final Message m = PooledLambda.obtainMessage(
- DisplayMagnification.this::requestRectangleOnScreen, left, top, right, bottom);
- mHandler.sendMessage(m);
+ DisplayMagnification::requestRectangleOnScreen, this,
+ left, top, right, bottom);
+ mControllerCtx.getHandler().sendMessage(m);
}
@Override
public void onRotationChanged(int rotation) {
// Treat as context change and reset
- final Message m = PooledLambda.obtainMessage(DisplayMagnification.this::resetIfNeeded,
- true);
- mHandler.sendMessage(m);
+ final Message m = PooledLambda.obtainMessage(MagnificationController::resetIfNeeded,
+ MagnificationController.this, mDisplayId, true);
+ mControllerCtx.getHandler().sendMessage(m);
}
@Override
public void onUserContextChanged() {
- final Message m = PooledLambda.obtainMessage(DisplayMagnification.this::resetIfNeeded,
- true);
- mHandler.sendMessage(m);
+ final Message m = PooledLambda.obtainMessage(MagnificationController::resetIfNeeded,
+ MagnificationController.this, mDisplayId, true);
+ mControllerCtx.getHandler().sendMessage(m);
}
/**
@@ -298,8 +304,9 @@
mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
} else {
final Message m = PooledLambda.obtainMessage(
- this.mSpecAnimationBridge::updateSentSpecMainThread, spec, animate);
- mHandler.sendMessage(m);
+ SpecAnimationBridge::updateSentSpecMainThread,
+ mSpecAnimationBridge, spec, animate);
+ mControllerCtx.getHandler().sendMessage(m);
}
}
@@ -313,30 +320,26 @@
}
void onMagnificationChangedLocked() {
- mAms.notifyMagnificationChanged(mMagnificationRegion,
+ mControllerCtx.getAms().notifyMagnificationChanged(mDisplayId, mMagnificationRegion,
getScale(), getCenterX(), getCenterY());
if (mUnregisterPending && !isMagnifying()) {
- unregisterInternalLocked();
+ unregister(mDeleteAfterUnregister);
}
}
+ @GuardedBy("mLock")
boolean magnificationRegionContains(float x, float y) {
- synchronized (mLock) {
- return mMagnificationRegion.contains((int) x, (int) y);
-
- }
+ return mMagnificationRegion.contains((int) x, (int) y);
}
+ @GuardedBy("mLock")
void getMagnificationBounds(@NonNull Rect outBounds) {
- synchronized (mLock) {
- outBounds.set(mMagnificationBounds);
- }
+ outBounds.set(mMagnificationBounds);
}
+ @GuardedBy("mLock")
void getMagnificationRegion(@NonNull Region outRegion) {
- synchronized (mLock) {
- outRegion.set(mMagnificationRegion);
- }
+ outRegion.set(mMagnificationRegion);
}
void requestRectangleOnScreen(int left, int top, int right, int bottom) {
@@ -392,94 +395,76 @@
outFrame.scale(1.0f / scale);
}
- /**
- * Resets magnification if last magnifying service is disabled.
- *
- * @param connectionId the connection ID be disabled.
- * @return {@code true} on success, {@code false} on failure
- */
- boolean resetIfNeeded(int connectionId) {
- if (mIdOfLastServiceToMagnify == connectionId) {
- return resetIfNeeded(true /*animate*/);
- }
- return false;
- }
-
+ @GuardedBy("mLock")
void setForceShowMagnifiableBounds(boolean show) {
if (mRegistered) {
- mWindowManager.setForceShowMagnifiableBounds(show);
+ mControllerCtx.getWindowManager().setForceShowMagnifiableBounds(
+ mDisplayId, show);
}
}
+ @GuardedBy("mLock")
boolean reset(boolean animate) {
- synchronized (mLock) {
- if (!mRegistered) {
- return false;
- }
- final MagnificationSpec spec = mCurrentMagnificationSpec;
- final boolean changed = !spec.isNop();
- if (changed) {
- spec.clear();
- onMagnificationChangedLocked();
- }
- mIdOfLastServiceToMagnify = INVALID_ID;
- sendSpecToAnimation(spec, animate);
- return changed;
+ if (!mRegistered) {
+ return false;
}
+ final MagnificationSpec spec = mCurrentMagnificationSpec;
+ final boolean changed = !spec.isNop();
+ if (changed) {
+ spec.clear();
+ onMagnificationChangedLocked();
+ }
+ mIdOfLastServiceToMagnify = INVALID_ID;
+ sendSpecToAnimation(spec, animate);
+ return changed;
}
-
+ @GuardedBy("mLock")
boolean setScale(float scale, float pivotX, float pivotY,
boolean animate, int id) {
-
- synchronized (mLock) {
- if (!mRegistered) {
- return false;
- }
- // Constrain scale immediately for use in the pivot calculations.
- scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
-
- final Rect viewport = mTempRect;
- mMagnificationRegion.getBounds(viewport);
- final MagnificationSpec spec = mCurrentMagnificationSpec;
- final float oldScale = spec.scale;
- final float oldCenterX
- = (viewport.width() / 2.0f - spec.offsetX + viewport.left) / oldScale;
- final float oldCenterY
- = (viewport.height() / 2.0f - spec.offsetY + viewport.top) / oldScale;
- final float normPivotX = (pivotX - spec.offsetX) / oldScale;
- final float normPivotY = (pivotY - spec.offsetY) / oldScale;
- final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
- final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
- final float centerX = normPivotX + offsetX;
- final float centerY = normPivotY + offsetY;
- mIdOfLastServiceToMagnify = id;
-
- return setScaleAndCenter(scale, centerX, centerY, animate, id);
+ if (!mRegistered) {
+ return false;
}
+ // Constrain scale immediately for use in the pivot calculations.
+ scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+
+ final Rect viewport = mTempRect;
+ mMagnificationRegion.getBounds(viewport);
+ final MagnificationSpec spec = mCurrentMagnificationSpec;
+ final float oldScale = spec.scale;
+ final float oldCenterX =
+ (viewport.width() / 2.0f - spec.offsetX + viewport.left) / oldScale;
+ final float oldCenterY =
+ (viewport.height() / 2.0f - spec.offsetY + viewport.top) / oldScale;
+ final float normPivotX = (pivotX - spec.offsetX) / oldScale;
+ final float normPivotY = (pivotY - spec.offsetY) / oldScale;
+ final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
+ final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
+ final float centerX = normPivotX + offsetX;
+ final float centerY = normPivotY + offsetY;
+ mIdOfLastServiceToMagnify = id;
+ return setScaleAndCenter(scale, centerX, centerY, animate, id);
}
+ @GuardedBy("mLock")
boolean setScaleAndCenter(float scale, float centerX, float centerY,
boolean animate, int id) {
-
- synchronized (mLock) {
- if (!mRegistered) {
- return false;
- }
- if (DEBUG) {
- Slog.i(LOG_TAG,
- "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
- + ", centerY = " + centerY + ", animate = " + animate
- + ", id = " + id
- + ")");
- }
- final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
- sendSpecToAnimation(mCurrentMagnificationSpec, animate);
- if (isMagnifying() && (id != INVALID_ID)) {
- mIdOfLastServiceToMagnify = id;
- }
- return changed;
+ if (!mRegistered) {
+ return false;
}
+ if (DEBUG) {
+ Slog.i(LOG_TAG,
+ "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
+ + ", centerY = " + centerY + ", animate = " + animate
+ + ", id = " + id
+ + ")");
+ }
+ final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
+ sendSpecToAnimation(mCurrentMagnificationSpec, animate);
+ if (isMagnifying() && (id != INVALID_ID)) {
+ mIdOfLastServiceToMagnify = id;
+ }
+ return changed;
}
/**
@@ -527,22 +512,21 @@
return changed;
}
+ @GuardedBy("mLock")
void offsetMagnifiedRegion(float offsetX, float offsetY, int id) {
- synchronized (mLock) {
- if (!mRegistered) {
- return;
- }
-
- final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
- final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
- if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
- onMagnificationChangedLocked();
- }
- if (id != INVALID_ID) {
- mIdOfLastServiceToMagnify = id;
- }
- sendSpecToAnimation(mCurrentMagnificationSpec, false);
+ if (!mRegistered) {
+ return;
}
+
+ final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
+ final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
+ if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
+ onMagnificationChangedLocked();
+ }
+ if (id != INVALID_ID) {
+ mIdOfLastServiceToMagnify = id;
+ }
+ sendSpecToAnimation(mCurrentMagnificationSpec, false);
}
boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
@@ -593,44 +577,38 @@
@Override
public String toString() {
- return "DisplayMagnification{" +
- "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec +
- ", mMagnificationRegion=" + mMagnificationRegion +
- ", mMagnificationBounds=" + mMagnificationBounds +
- ", mDisplayId=" + mDisplayId +
- ", mUserId=" + mUserId +
- ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify +
- ", mRegistered=" + mRegistered +
- ", mUnregisterPending=" + mUnregisterPending +
- '}';
+ return "DisplayMagnification["
+ + "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec
+ + ", mMagnificationRegion=" + mMagnificationRegion
+ + ", mMagnificationBounds=" + mMagnificationBounds
+ + ", mDisplayId=" + mDisplayId
+ + ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify
+ + ", mRegistered=" + mRegistered
+ + ", mUnregisterPending=" + mUnregisterPending
+ + ']';
}
-
}
- public MagnificationController(Context context, AccessibilityManagerService ams, Object lock) {
- this(context, ams, lock, null, LocalServices.getService(WindowManagerInternal.class),
- new ValueAnimator(), new SettingsBridge(context.getContentResolver()));
- mHandler = new Handler(context.getMainLooper());
+ /**
+ * MagnificationController Constructor
+ */
+ public MagnificationController(@NonNull Context context,
+ @NonNull AccessibilityManagerService ams, @NonNull Object lock) {
+ this(new ControllerContext(context, ams,
+ LocalServices.getService(WindowManagerInternal.class),
+ new Handler(context.getMainLooper()),
+ context.getResources().getInteger(R.integer.config_longAnimTime)), lock);
}
- public MagnificationController(
- Context context,
- AccessibilityManagerService ams,
- Object lock,
- Handler handler,
- WindowManagerInternal windowManagerInternal,
- ValueAnimator valueAnimator,
- SettingsBridge settingsBridge) {
- mHandler = handler;
- mWindowManager = windowManagerInternal;
- mMainThreadId = context.getMainLooper().getThread().getId();
- mAms = ams;
- mScreenStateObserver = new ScreenStateObserver(context, this);
+ /**
+ * Constructor for tests
+ */
+ @VisibleForTesting
+ public MagnificationController(@NonNull ControllerContext ctx, @NonNull Object lock) {
+ mControllerCtx = ctx;
mLock = lock;
- mSettingsBridge = settingsBridge;
- //TODO (multidisplay): Magnification is supported only for the default display.
- mDisplay = new DisplayMagnification(Display.DEFAULT_DISPLAY,
- new SpecAnimationBridge(context, mLock, mWindowManager, valueAnimator));
+ mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
+ mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
}
/**
@@ -639,54 +617,114 @@
*
* This tracking imposes a cost on the system, so we avoid tracking this data unless it's
* required.
+ *
+ * @param displayId The logical display id.
*/
- public void register() {
+ public void register(int displayId) {
synchronized (mLock) {
- mScreenStateObserver.register();
+ DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ display = new DisplayMagnification(displayId);
+ }
+ if (display.isRegistered()) {
+ return;
+ }
+ if (display.register()) {
+ mDisplays.put(displayId, display);
+ mScreenStateObserver.registerIfNecessary();
+ }
}
- mDisplay.register();
}
/**
* Stop requiring tracking the magnification region. We may remain registered while we
* reset magnification.
- */
- public void unregister() {
- synchronized (mLock) {
- mScreenStateObserver.unregister();
- }
- mDisplay.unregister();
- }
-
- /**
- * Check if we are registered. Note that we may be planning to unregister at any moment.
*
- * @return {@code true} if the controller is registered. {@code false} otherwise.
+ * @param displayId The logical display id.
*/
- public boolean isRegisteredLocked() {
- return mDisplay.isRegisteredLocked();
+ public void unregister(int displayId) {
+ synchronized (mLock) {
+ unregisterLocked(displayId, false);
+ }
}
/**
+ * Stop tracking all displays' magnification region.
+ */
+ public void unregisterAll() {
+ synchronized (mLock) {
+ // display will be removed from array after unregister, we need to clone it to
+ // prevent error.
+ final SparseArray<DisplayMagnification> displays = mDisplays.clone();
+ for (int i = 0; i < displays.size(); i++) {
+ unregisterLocked(displays.keyAt(i), false);
+ }
+ }
+ }
+
+ /**
+ * Remove the display magnification with given id.
+ *
+ * @param displayId The logical display id.
+ */
+ public void onDisplayRemoved(int displayId) {
+ synchronized (mLock) {
+ unregisterLocked(displayId, true);
+ }
+ }
+
+ /**
+ * Check if we are registered on specified display. Note that we may be planning to unregister
+ * at any moment.
+ *
+ * @return {@code true} if the controller is registered on specified display.
+ * {@code false} otherwise.
+ *
+ * @param displayId The logical display id.
+ */
+ public boolean isRegistered(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.isRegistered();
+ }
+ }
+
+ /**
+ * @param displayId The logical display id.
* @return {@code true} if magnification is active, e.g. the scale
* is > 1, {@code false} otherwise
*/
- public boolean isMagnifying() {
- return mDisplay.isMagnifying();
+ public boolean isMagnifying(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.isMagnifying();
+ }
}
/**
* Returns whether the magnification region contains the specified
* screen-relative coordinates.
*
+ * @param displayId The logical display id.
* @param x the screen-relative X coordinate to check
* @param y the screen-relative Y coordinate to check
* @return {@code true} if the coordinate is contained within the
* magnified region, or {@code false} otherwise
*/
- public boolean magnificationRegionContains(float x, float y) {
- return mDisplay.magnificationRegionContains(x, y);
-
+ public boolean magnificationRegionContains(int displayId, float x, float y) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.magnificationRegionContains(x, y);
+ }
}
/**
@@ -694,11 +732,18 @@
* magnification region. If magnification is not enabled, the returned
* bounds will be empty.
*
+ * @param displayId The logical display id.
* @param outBounds rect to populate with the bounds of the magnified
* region
*/
- public void getMagnificationBounds(@NonNull Rect outBounds) {
- mDisplay.getMagnificationBounds(outBounds);
+ public void getMagnificationBounds(int displayId, @NonNull Rect outBounds) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.getMagnificationBounds(outBounds);
+ }
}
/**
@@ -706,76 +751,122 @@
* region. If magnification is not enabled, then the returned region
* will be empty.
*
+ * @param displayId The logical display id.
* @param outRegion the region to populate
*/
- public void getMagnificationRegion(@NonNull Region outRegion) {
- mDisplay.getMagnificationRegion(outRegion);
+ public void getMagnificationRegion(int displayId, @NonNull Region outRegion) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.getMagnificationRegion(outRegion);
+ }
}
/**
* Returns the magnification scale. If an animation is in progress,
* this reflects the end state of the animation.
*
+ * @param displayId The logical display id.
* @return the scale
*/
- public float getScale() {
- return mDisplay.getScale();
+ public float getScale(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return 1.0f;
+ }
+ return display.getScale();
+ }
}
/**
* Returns the X offset of the magnification viewport. If an animation
* is in progress, this reflects the end state of the animation.
*
+ * @param displayId The logical display id.
* @return the X offset
*/
- public float getOffsetX() {
- return mDisplay.getOffsetX();
+ public float getOffsetX(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return 0.0f;
+ }
+ return display.getOffsetX();
+ }
}
-
/**
* Returns the screen-relative X coordinate of the center of the
* magnification viewport.
*
+ * @param displayId The logical display id.
* @return the X coordinate
*/
- public float getCenterX() {
- return mDisplay.getCenterX();
+ public float getCenterX(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return 0.0f;
+ }
+ return display.getCenterX();
+ }
}
/**
* Returns the Y offset of the magnification viewport. If an animation
* is in progress, this reflects the end state of the animation.
*
+ * @param displayId The logical display id.
* @return the Y offset
*/
- public float getOffsetY() {
- return mDisplay.getOffsetY();
+ public float getOffsetY(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return 0.0f;
+ }
+ return display.getOffsetY();
+ }
}
/**
* Returns the screen-relative Y coordinate of the center of the
* magnification viewport.
*
+ * @param displayId The logical display id.
* @return the Y coordinate
*/
- public float getCenterY() {
- return mDisplay.getCenterY();
+ public float getCenterY(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return 0.0f;
+ }
+ return display.getCenterY();
+ }
}
/**
* Resets the magnification scale and center, optionally animating the
* transition.
*
+ * @param displayId The logical display id.
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
- public boolean reset(boolean animate) {
-
- return mDisplay.reset(animate);
-
+ public boolean reset(int displayId, boolean animate) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.reset(animate);
+ }
}
/**
@@ -783,6 +874,7 @@
* optionally animating the transition. If animation is disabled, the
* transition is immediate.
*
+ * @param displayId The logical display id.
* @param scale the target scale, must be >= 1
* @param pivotX the screen-relative X coordinate around which to scale
* @param pivotY the screen-relative Y coordinate around which to scale
@@ -792,15 +884,22 @@
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
- public boolean setScale(float scale, float pivotX, float pivotY, boolean animate, int id) {
- return mDisplay.
- setScale(scale, pivotX, pivotY, animate, id);
+ public boolean setScale(int displayId, float scale, float pivotX, float pivotY,
+ boolean animate, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.setScale(scale, pivotX, pivotY, animate, id);
+ }
}
/**
* Sets the center of the magnified region, optionally animating the
* transition. If animation is disabled, the transition is immediate.
*
+ * @param displayId The logical display id.
* @param centerX the screen-relative X coordinate around which to
* center
* @param centerY the screen-relative Y coordinate around which to
@@ -811,9 +910,14 @@
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
- public boolean setCenter(float centerX, float centerY, boolean animate, int id) {
- return mDisplay.
- setScaleAndCenter(Float.NaN, centerX, centerY, animate, id);
+ public boolean setCenter(int displayId, float centerX, float centerY, boolean animate, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.setScaleAndCenter(Float.NaN, centerX, centerY, animate, id);
+ }
}
/**
@@ -821,6 +925,7 @@
* animating the transition. If animation is disabled, the transition
* is immediate.
*
+ * @param displayId The logical display id.
* @param scale the target scale, or {@link Float#NaN} to leave unchanged
* @param centerX the screen-relative X coordinate around which to
* center and scale, or {@link Float#NaN} to leave unchanged
@@ -832,53 +937,66 @@
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
- public boolean setScaleAndCenter(
- float scale, float centerX, float centerY, boolean animate, int id) {
- return mDisplay.
- setScaleAndCenter(scale, centerX, centerY, animate, id);
+ public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
+ boolean animate, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.setScaleAndCenter(scale, centerX, centerY, animate, id);
+ }
}
/**
* Offsets the magnified region. Note that the offsetX and offsetY values actually move in the
* opposite direction as the offsets passed in here.
*
+ * @param displayId The logical display id.
* @param offsetX the amount in pixels to offset the region in the X direction, in current
* screen pixels.
* @param offsetY the amount in pixels to offset the region in the Y direction, in current
* screen pixels.
* @param id the ID of the service requesting the change
*/
- public void offsetMagnifiedRegion(float offsetX, float offsetY, int id) {
- mDisplay.offsetMagnifiedRegion(offsetX, offsetY,
- id);
+ public void offsetMagnifiedRegion(int displayId, float offsetX, float offsetY, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.offsetMagnifiedRegion(offsetX, offsetY, id);
+ }
}
/**
* Get the ID of the last service that changed the magnification spec.
*
+ * @param displayId The logical display id.
* @return The id
*/
- public int getIdOfLastServiceToMagnify() {
- return mDisplay.getIdOfLastServiceToMagnify();
+ public int getIdOfLastServiceToMagnify(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return -1;
+ }
+ return display.getIdOfLastServiceToMagnify();
+ }
}
/**
- * Persists the current magnification scale to the current user's settings.
+ * Persists the default display magnification scale to the current user's settings.
*/
public void persistScale() {
- persistScale(Display.DEFAULT_DISPLAY);
- }
- /**
- * Persists the current magnification scale to the current user's settings.
- */
- public void persistScale(int displayId) {
- final float scale = mDisplay.getScale();
+ // TODO: b/123047354, Need support multi-display?
+ final float scale = getScale(Display.DEFAULT_DISPLAY);
final int userId = mUserId;
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
- mSettingsBridge.putMagnificationScale(scale, displayId, userId);
+ mControllerCtx.putMagnificationScale(scale, userId);
return null;
}
}.execute();
@@ -892,7 +1010,7 @@
* scale if none is available
*/
public float getPersistedScale() {
- return mSettingsBridge.getMagnificationScale(Display.DEFAULT_DISPLAY, mUserId);
+ return mControllerCtx.getMagnificationScale(mUserId);
}
/**
@@ -901,50 +1019,136 @@
* @param userId the currently active user ID
*/
public void setUserId(int userId) {
- if (mUserId != userId) {
- mUserId = userId;
+ if (mUserId == userId) {
+ return;
+ }
+ mUserId = userId;
+ resetAllIfNeeded(false);
+ }
- synchronized (mLock) {
- if (isMagnifying()) {
- reset(false);
- }
+ /**
+ * Resets all displays' magnification if last magnifying service is disabled.
+ *
+ * @param connectionId
+ */
+ public void resetAllIfNeeded(int connectionId) {
+ synchronized (mLock) {
+ for (int i = 0; i < mDisplays.size(); i++) {
+ resetIfNeeded(mDisplays.keyAt(i), connectionId);
}
}
}
- /**
+ /**
* Resets magnification if magnification and auto-update are both enabled.
*
+ * @param displayId The logical display id.
* @param animate whether the animate the transition
- * @return whether was {@link #isMagnifying magnifying}
+ * @return whether was {@link #isMagnifying(int) magnifying}
*/
- public boolean resetIfNeeded(boolean animate) {
- return mDisplay.resetIfNeeded(animate);
+ boolean resetIfNeeded(int displayId, boolean animate) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null || !display.isMagnifying()) {
+ return false;
+ }
+ display.reset(animate);
+ return true;
+ }
}
/**
* Resets magnification if last magnifying service is disabled.
*
+ * @param displayId The logical display id.
* @param connectionId the connection ID be disabled.
* @return {@code true} on success, {@code false} on failure
*/
- public boolean resetIfNeeded(int connectionId) {
- return mDisplay.resetIfNeeded(connectionId);
+ boolean resetIfNeeded(int displayId, int connectionId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null || !display.isMagnifying()
+ || connectionId != display.getIdOfLastServiceToMagnify()) {
+ return false;
+ }
+ display.reset(true);
+ return true;
+ }
}
- void setForceShowMagnifiableBounds(boolean show) {
- mDisplay.setForceShowMagnifiableBounds(show);
+ void setForceShowMagnifiableBounds(int displayId, boolean show) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.setForceShowMagnifiableBounds(show);
+ }
}
private void onScreenTurnedOff() {
final Message m = PooledLambda.obtainMessage(
- mDisplay::resetIfNeeded, false);
- mHandler.sendMessage(m);
+ MagnificationController::resetAllIfNeeded, this, false);
+ mControllerCtx.getHandler().sendMessage(m);
+ }
+
+ private void resetAllIfNeeded(boolean animate) {
+ synchronized (mLock) {
+ for (int i = 0; i < mDisplays.size(); i++) {
+ resetIfNeeded(mDisplays.keyAt(i), animate);
+ }
+ }
+ }
+
+ private void unregisterLocked(int displayId, boolean delete) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ if (!display.isRegistered()) {
+ if (delete) {
+ mDisplays.remove(displayId);
+ }
+ return;
+ }
+ if (!display.isMagnifying()) {
+ display.unregister(delete);
+ } else {
+ display.unregisterPending(delete);
+ }
+ }
+
+ /**
+ * Callbacks from DisplayMagnification after display magnification unregistered. It will remove
+ * DisplayMagnification instance if delete is true, and unregister screen state if
+ * there is no registered display magnification.
+ */
+ private void unregisterCallbackLocked(int displayId, boolean delete) {
+ if (delete) {
+ mDisplays.remove(displayId);
+ }
+ // unregister screen state if necessary
+ boolean hasRegister = false;
+ for (int i = 0; i < mDisplays.size(); i++) {
+ final DisplayMagnification display = mDisplays.valueAt(i);
+ hasRegister = display.isRegistered();
+ if (hasRegister) {
+ break;
+ }
+ }
+ if (!hasRegister) {
+ mScreenStateObserver.unregister();
+ }
}
@Override
public String toString() {
- return mDisplay.toString();
+ StringBuilder builder = new StringBuilder();
+ builder.append("MagnificationController[");
+ builder.append("mUserId=").append(mUserId);
+ builder.append(", mDisplays=").append(mDisplays);
+ builder.append("]");
+ return builder.toString();
}
/**
@@ -952,7 +1156,7 @@
* updates to the window manager.
*/
private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener {
- private final WindowManagerInternal mWindowManager;
+ private final ControllerContext mControllerCtx;
/**
* The magnification spec that was sent to the window manager. This should
@@ -973,16 +1177,17 @@
private final Object mLock;
+ private final int mDisplayId;
+
@GuardedBy("mLock")
private boolean mEnabled = false;
- private SpecAnimationBridge(Context context, Object lock, WindowManagerInternal wm,
- ValueAnimator animator) {
+ private SpecAnimationBridge(ControllerContext ctx, Object lock, int displayId) {
+ mControllerCtx = ctx;
mLock = lock;
- mWindowManager = wm;
- final long animationDuration = context.getResources().getInteger(
- R.integer.config_longAnimTime);
- mValueAnimator = animator;
+ mDisplayId = displayId;
+ final long animationDuration = mControllerCtx.getAnimationDuration();
+ mValueAnimator = mControllerCtx.newValueAnimator();
mValueAnimator.setDuration(animationDuration);
mValueAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
mValueAnimator.setFloatValues(0.0f, 1.0f);
@@ -999,7 +1204,8 @@
mEnabled = enabled;
if (!mEnabled) {
mSentMagnificationSpec.clear();
- mWindowManager.setMagnificationSpec(mSentMagnificationSpec);
+ mControllerCtx.getWindowManager().setMagnificationSpec(
+ mDisplayId, mSentMagnificationSpec);
}
}
}
@@ -1031,7 +1237,8 @@
}
mSentMagnificationSpec.setTo(spec);
- mWindowManager.setMagnificationSpec(spec);
+ mControllerCtx.getWindowManager().setMagnificationSpec(
+ mDisplayId, mSentMagnificationSpec);
}
}
@@ -1054,9 +1261,7 @@
mTmpMagnificationSpec.offsetY = mStartMagnificationSpec.offsetY +
(mEndMagnificationSpec.offsetY - mStartMagnificationSpec.offsetY)
* fract;
- synchronized (mLock) {
- setMagnificationSpecLocked(mTmpMagnificationSpec);
- }
+ setMagnificationSpecLocked(mTmpMagnificationSpec);
}
}
}
@@ -1072,7 +1277,7 @@
mController = controller;
}
- public void register() {
+ public void registerIfNecessary() {
if (!mRegistered) {
mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
mRegistered = true;
@@ -1092,26 +1297,97 @@
}
}
- // Extra class to get settings so tests can mock it
- public static class SettingsBridge {
- private final ContentResolver mContentResolver;
+ /**
+ * This class holds resources used between the classes in MagnificationController, and
+ * functions for tests to mock it.
+ */
+ @VisibleForTesting
+ public static class ControllerContext {
+ private final Context mContext;
+ private final AccessibilityManagerService mAms;
+ private final WindowManagerInternal mWindowManager;
+ private final Handler mHandler;
+ private final Long mAnimationDuration;
- public SettingsBridge(ContentResolver contentResolver) {
- mContentResolver = contentResolver;
+ /**
+ * Constructor for ControllerContext.
+ */
+ public ControllerContext(@NonNull Context context,
+ @NonNull AccessibilityManagerService ams,
+ @NonNull WindowManagerInternal windowManager,
+ @NonNull Handler handler,
+ long animationDuration) {
+ mContext = context;
+ mAms = ams;
+ mWindowManager = windowManager;
+ mHandler = handler;
+ mAnimationDuration = animationDuration;
}
- public void putMagnificationScale(float value, int displayId, int userId) {
- Settings.Secure.putFloatForUser(mContentResolver,
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE + (
- Display.DEFAULT_DISPLAY == displayId ? "" : displayId),
- value, userId);
+ /**
+ * @return A context.
+ */
+ @NonNull
+ public Context getContext() {
+ return mContext;
}
- public float getMagnificationScale(int displayId, int userId) {
- return Settings.Secure.getFloatForUser(mContentResolver,
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE
- + (Display.DEFAULT_DISPLAY == displayId ? "" : displayId),
+ /**
+ * @return AccessibilityManagerService
+ */
+ @NonNull
+ public AccessibilityManagerService getAms() {
+ return mAms;
+ }
+
+ /**
+ * @return WindowManagerInternal
+ */
+ @NonNull
+ public WindowManagerInternal getWindowManager() {
+ return mWindowManager;
+ }
+
+ /**
+ * @return Handler for main looper
+ */
+ @NonNull
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ /**
+ * Create a new ValueAnimator.
+ *
+ * @return ValueAnimator
+ */
+ @NonNull
+ public ValueAnimator newValueAnimator() {
+ return new ValueAnimator();
+ }
+
+ /**
+ * Write Settings of magnification scale.
+ */
+ public void putMagnificationScale(float value, int userId) {
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, value, userId);
+ }
+
+ /**
+ * Get Settings of magnification scale.
+ */
+ public float getMagnificationScale(int userId) {
+ return Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
DEFAULT_MAGNIFICATION_SCALE, userId);
}
+
+ /**
+ * @return Configuration of animation duration.
+ */
+ public long getAnimationDuration() {
+ return mAnimationDuration;
+ }
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 80049e8..49db488 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -43,6 +43,7 @@
import android.util.MathUtils;
import android.util.Slog;
import android.util.TypedValue;
+import android.view.Display;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
@@ -251,14 +252,16 @@
mScreenStateReceiver.unregister();
}
// Check if need to reset when MagnificationGestureHandler is the last magnifying service.
- mMagnificationController.resetIfNeeded(
+ mMagnificationController.resetAllIfNeeded(
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
clearAndTransitionToStateDetecting();
}
void notifyShortcutTriggered() {
if (mDetectShortcutTrigger) {
- boolean wasMagnifying = mMagnificationController.resetIfNeeded(/* animate */ true);
+ // TODO: multi-display support for magnification gesture handler
+ boolean wasMagnifying = mMagnificationController.resetIfNeeded(Display.DEFAULT_DISPLAY,
+ /* animate */ true);
if (wasMagnifying) {
clearAndTransitionToStateDetecting();
} else {
@@ -419,8 +422,9 @@
Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
+ " scrollY: " + distanceY);
}
- mMagnificationController.offsetMagnifiedRegion(distanceX, distanceY,
- AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ // TODO: multi-display support for magnification gesture handler
+ mMagnificationController.offsetMagnifiedRegion(Display.DEFAULT_DISPLAY, distanceX,
+ distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
return /* event consumed: */ true;
}
@@ -436,7 +440,8 @@
return mScaling;
}
- final float initialScale = mMagnificationController.getScale();
+ // TODO: multi-display support for magnification gesture handler
+ final float initialScale = mMagnificationController.getScale(Display.DEFAULT_DISPLAY);
final float targetScale = initialScale * detector.getScaleFactor();
// Don't allow a gesture to move the user further outside the
@@ -458,7 +463,8 @@
final float pivotX = detector.getFocusX();
final float pivotY = detector.getFocusY();
if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x");
- mMagnificationController.setScale(scale, pivotX, pivotY, false,
+ // TODO: multi-display support for magnification gesture handler
+ mMagnificationController.setScale(Display.DEFAULT_DISPLAY, scale, pivotX, pivotY, false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
return /* handled: */ true;
}
@@ -518,8 +524,10 @@
}
final float eventX = event.getX();
final float eventY = event.getY();
- if (mMagnificationController.magnificationRegionContains(eventX, eventY)) {
- mMagnificationController.setCenter(eventX, eventY,
+ // TODO: multi-display support for magnification gesture handler
+ if (mMagnificationController.magnificationRegionContains(
+ Display.DEFAULT_DISPLAY, eventX, eventY)) {
+ mMagnificationController.setCenter(Display.DEFAULT_DISPLAY, eventX, eventY,
/* animate */ mLastMoveOutsideMagnifiedRegion,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
mLastMoveOutsideMagnifiedRegion = false;
@@ -657,8 +665,9 @@
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+ // TODO: multi-display support for magnification gesture handler
if (!mMagnificationController.magnificationRegionContains(
- event.getX(), event.getY())) {
+ Display.DEFAULT_DISPLAY, event.getX(), event.getY())) {
transitionToDelegatingStateAndClear();
@@ -667,11 +676,16 @@
// 3tap and hold
afterLongTapTimeoutTransitionToDraggingState(event);
+ } else if (isTapOutOfDistanceSlop()) {
+
+ transitionToDelegatingStateAndClear();
+
} else if (mDetectTripleTap
// If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
// to ensure reachability of
// STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
- || mMagnificationController.isMagnifying()) {
+ // TODO: multi-display support for magnification gesture handler
+ || mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
afterMultiTapTimeoutTransitionToDelegatingState();
@@ -683,7 +697,8 @@
}
break;
case ACTION_POINTER_DOWN: {
- if (mMagnificationController.isMagnifying()) {
+ // TODO: multi-display support for magnification gesture handler
+ if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
transitionTo(mPanningScalingState);
clear();
} else {
@@ -712,8 +727,9 @@
mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
+ // TODO: multi-display support for magnification gesture handler
if (!mMagnificationController.magnificationRegionContains(
- event.getX(), event.getY())) {
+ Display.DEFAULT_DISPLAY, event.getX(), event.getY())) {
transitionToDelegatingStateAndClear();
@@ -864,7 +880,8 @@
clear();
// Toggle zoom
- if (mMagnificationController.isMagnifying()) {
+ // TODO: multi-display support for magnification gesture handler
+ if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
zoomOff();
} else {
zoomOn(up.getX(), up.getY());
@@ -876,8 +893,9 @@
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()");
clear();
+ // TODO: multi-display support for magnification gesture handler
mViewportDraggingState.mZoomedInBeforeDrag =
- mMagnificationController.isMagnifying();
+ mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY);
zoomOn(down.getX(), down.getY());
@@ -904,7 +922,33 @@
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "setShortcutTriggered(" + state + ")");
mShortcutTriggered = state;
- mMagnificationController.setForceShowMagnifiableBounds(state);
+ // TODO: multi-display support for magnification gesture handler
+ mMagnificationController.setForceShowMagnifiableBounds(Display.DEFAULT_DISPLAY, state);
+ }
+
+ /**
+ * Detects if last action down is out of distance slop between with previous
+ * one, when triple tap is enabled.
+ *
+ * @return true if tap is out of distance slop
+ */
+ boolean isTapOutOfDistanceSlop() {
+ if (!mDetectTripleTap) return false;
+ if (mPreLastDown == null || mLastDown == null) {
+ return false;
+ }
+ final boolean outOfDistanceSlop =
+ GestureUtils.distance(mPreLastDown, mLastDown) > mMultiTapMaxDistance;
+ if (tapCount() > 0) {
+ return outOfDistanceSlop;
+ }
+ // There's no tap in the queue here. We still need to check if this is the case that
+ // user tap screen quickly and out of distance slop.
+ if (outOfDistanceSlop
+ && !GestureUtils.isTimedOut(mPreLastDown, mLastDown, mMultiTapMaxDelay)) {
+ return true;
+ }
+ return false;
}
}
@@ -914,7 +958,8 @@
final float scale = MathUtils.constrain(
mMagnificationController.getPersistedScale(),
MIN_SCALE, MAX_SCALE);
- mMagnificationController.setScaleAndCenter(
+ // TODO: multi-display support for magnification gesture handler
+ mMagnificationController.setScaleAndCenter(Display.DEFAULT_DISPLAY,
scale, centerX, centerY,
/* animate */ true,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -922,8 +967,8 @@
private void zoomOff() {
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()");
-
- mMagnificationController.reset(/* animate */ true);
+ // TODO: multi-display support for magnification gesture handler
+ mMagnificationController.reset(Display.DEFAULT_DISPLAY, /* animate */ true);
}
private static MotionEvent recycleAndNullify(@Nullable MotionEvent event) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index c992da4..2e45fa7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -18,14 +18,13 @@
import static android.Manifest.permission.MANAGE_AUTO_FILL;
import static android.content.Context.AUTOFILL_MANAGER_SERVICE;
-import static android.util.DebugUtils.flagsToString;
import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS;
+import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sFullScreenMode;
import static com.android.server.autofill.Helper.sVerbose;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -61,6 +60,7 @@
import android.util.SparseArray;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.SmartSuggestionMode;
import android.view.autofill.AutofillManagerInternal;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManager;
@@ -80,8 +80,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -102,26 +100,6 @@
private static final Object sLock = AutofillManagerService.class;
- /**
- * IME supports Smart Suggestions.
- */
- // NOTE: must be public because of flagsToString()
- public static final int FLAG_SMART_SUGGESTION_IME = 0x1;
-
- /**
- * System supports Smarts Suggestions (as a popup-window similar to standard Autofill).
- */
- // NOTE: must be public because of flagsToString()
- public static final int FLAG_SMART_SUGGESTION_SYSTEM = 0x2;
-
- /** @hide */
- @IntDef(flag = true, prefix = { "FLAG_SMART_SUGGESTION_" }, value = {
- FLAG_SMART_SUGGESTION_IME,
- FLAG_SMART_SUGGESTION_SYSTEM
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface SmartSuggestionMode {}
-
static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions";
private static final char COMPAT_PACKAGE_DELIMITER = ':';
@@ -484,7 +462,7 @@
Settings.Global.AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS, 0);
if (sDebug) {
Slog.d(TAG, "setSmartSuggestionEmulationFromSettings(): "
- + smartSuggestionFlagsToString(flags));
+ + getSmartSuggestionModeToString(flags));
}
synchronized (mLock) {
@@ -698,10 +676,6 @@
}
}
- static String smartSuggestionFlagsToString(int flags) {
- return flagsToString(AutofillManagerService.class, "FLAG_SMART_SUGGESTION_", flags);
- }
-
private final class LocalService extends AutofillManagerInternal {
@Override
public void onBackKeyPressed() {
@@ -1251,7 +1225,7 @@
pw.println(getWhitelistedCompatModePackagesFromSettings());
if (mSupportedSmartSuggestionModes != 0) {
pw.print("Smart Suggestion modes: ");
- pw.println(smartSuggestionFlagsToString(mSupportedSmartSuggestionModes));
+ pw.println(getSmartSuggestionModeToString(mSupportedSmartSuggestionModes));
}
if (showHistory) {
pw.println(); pw.println("Requests history:"); pw.println();
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 954b67e..8886ee2 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -63,6 +63,7 @@
import android.util.TimeUtils;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.SmartSuggestionMode;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
@@ -72,7 +73,6 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.LocalServices;
import com.android.server.autofill.AutofillManagerService.AutofillCompatState;
-import com.android.server.autofill.AutofillManagerService.SmartSuggestionMode;
import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.infra.AbstractPerUserSystemService;
@@ -855,7 +855,6 @@
@GuardedBy("mLock")
@SmartSuggestionMode int getSupportedSmartSuggestionModesLocked() {
- // TODO(b/111330312): once we support IME, we need to set it per-user (OR'ed with master)
return mMaster.getSupportedSmartSuggestionModesLocked();
}
@@ -1049,7 +1048,7 @@
componentName, mUserId, new RemoteAugmentedAutofillServiceCallbacks() {
@Override
public void onServiceDied(@NonNull RemoteAugmentedAutofillService service) {
- // TODO(b/111330312): properly implement
+ // TODO(b/123100811): properly implement
Slog.w(TAG, "remote augmented autofill service died");
}
}, mMaster.isInstantServiceAllowed(), mMaster.verbose);
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 5d8d8fa..9b863a9 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -113,7 +113,7 @@
scheduleAsyncRequest((s) -> s.onDestroyAllFillWindowsRequest());
}
- // TODO(b/111330312): inline into PendingAutofillRequest if it doesn't have any other subclass
+ // TODO(b/123100811): inline into PendingAutofillRequest if it doesn't have any other subclass
private abstract static class MyPendingRequest
extends PendingRequest<RemoteAugmentedAutofillService, IAugmentedAutofillService> {
protected final int mSessionId;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 7dfd8fe..194332a 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -23,11 +23,10 @@
import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
+import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
+import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static com.android.server.autofill.AutofillManagerService.FLAG_SMART_SUGGESTION_IME;
-import static com.android.server.autofill.AutofillManagerService.FLAG_SMART_SUGGESTION_SYSTEM;
-import static com.android.server.autofill.AutofillManagerService.smartSuggestionFlagsToString;
import static com.android.server.autofill.Helper.getNumericValue;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
@@ -88,6 +87,7 @@
import android.view.KeyEvent;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.SmartSuggestionMode;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
import android.view.autofill.IAutofillWindowPresenter;
@@ -97,7 +97,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.ArrayUtils;
-import com.android.server.autofill.AutofillManagerService.SmartSuggestionMode;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.autofill.ui.PendingUi;
@@ -250,7 +249,7 @@
/**
* Destroys the augmented Autofill UI.
*/
- // TODO(b/111330312): this runnable is called when the Autofill session is destroyed, the
+ // TODO(b/123099468): this runnable is called when the Autofill session is destroyed, the
// main reason being the cases where user tap HOME.
// Right now it's completely destroying the UI, but we need to decide whether / how to
// properly recover it later (for example, if the user switches back to the activity,
@@ -261,6 +260,12 @@
private Runnable mAugmentedAutofillDestroyer;
/**
+ * List of {@link MetricsEvent#AUTOFILL_AUGMENTED_REQUEST} metrics.
+ */
+ @GuardedBy("mLock")
+ private ArrayList<LogMaker> mAugmentedRequestsLogs;
+
+ /**
* Receiver of assist data from the app's {@link Activity}.
*/
private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() {
@@ -2541,8 +2546,8 @@
}
mService.resetLastResponse();
- // The default autofill service cannot fullfill the request, let's check if the intelligence
- // service can.
+ // The default autofill service cannot fullfill the request, let's check if the augmented
+ // autofill service can.
mAugmentedAutofillDestroyer = triggerAugmentedAutofillLocked();
if (mAugmentedAutofillDestroyer == null) {
if (sVerbose) {
@@ -2553,7 +2558,7 @@
notifyUnavailableToClient(AutofillManager.STATE_FINISHED);
removeSelf();
} else {
- // TODO(b/111330312, b/119638958): must set internal state so when user focus other
+ // TODO(b/123099468, b/119638958): must set internal state so when user focus other
// fields it does not generate a new call to the standard autofill service (right now
// it does). Must also add CTS tests to exercise this scenario.
if (sVerbose) {
@@ -2568,7 +2573,7 @@
*
* @return callback to destroy the autofill UI, or {@code null} if not supported.
*/
- // TODO(b/111330312): might need to call it in other places, like when the service returns a
+ // TODO(b/123099468): might need to call it in other places, like when the service returns a
// non-null response but without datasets (for example, just SaveInfo)
@GuardedBy("mLock")
private Runnable triggerAugmentedAutofillLocked() {
@@ -2588,14 +2593,12 @@
// Define which mode will be used
final int mode;
- if ((supportedModes & FLAG_SMART_SUGGESTION_IME) != 0) {
- // TODO(b/111330312): support it :-)
- Slog.w(TAG, "Smart Suggestions on IME not supported yet");
- return null;
- } else if ((supportedModes & FLAG_SMART_SUGGESTION_SYSTEM) != 0) {
+ if ((supportedModes & FLAG_SMART_SUGGESTION_SYSTEM) != 0) {
mode = FLAG_SMART_SUGGESTION_SYSTEM;
+ } else if ((supportedModes & AutofillManager.FLAG_SMART_SUGGESTION_LEGACY) != 0) {
+ mode = AutofillManager.FLAG_SMART_SUGGESTION_LEGACY;
} else {
- Slog.w(TAG, "Unsupported Smart Suggestion Mode: " + supportedModes);
+ Slog.w(TAG, "Unsupported Smart Suggestion mode: " + supportedModes);
return null;
}
@@ -2605,8 +2608,10 @@
}
if (sVerbose) {
- Slog.v(TAG, "calling IntelligenseService on view " + mCurrentViewId
- + " using suggestion mode " + smartSuggestionFlagsToString(mode)
+ Slog.v(TAG, "calling Augmented Autofill Service ("
+ + remoteService.getComponentName().toShortString() + ") on view "
+ + mCurrentViewId + " using suggestion mode "
+ + getSmartSuggestionModeToString(mode)
+ " when server returned null for session " + this.id);
}
@@ -2620,8 +2625,15 @@
final AutofillValue currentValue = mViewStates.get(mCurrentViewId).getCurrentValue();
// TODO(b/111330312): we might need to add a new state in the AutofillManager to optimize
- // furgher AFM -> AFMS calls.
- // TODO(b/119638958): add CTS tests
+ // further AFM -> AFMS calls.
+
+ if (mAugmentedRequestsLogs == null) {
+ mAugmentedRequestsLogs = new ArrayList<>();
+ }
+ final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_AUGMENTED_REQUEST,
+ remoteService.getComponentName().getPackageName());
+ mAugmentedRequestsLogs.add(log);
+
remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, mCurrentViewId,
currentValue);
@@ -2912,6 +2924,11 @@
if (mAugmentedAutofillDestroyer != null) {
pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer");
}
+ if (mAugmentedRequestsLogs != null) {
+ pw.print(prefix); pw.print("number augmented requests: ");
+ pw.println(mAugmentedRequestsLogs.size());
+ }
+
mRemoteFillService.dump(prefix, pw);
}
@@ -3053,8 +3070,26 @@
mMetricsLogger.write(log);
}
}
- mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED)
- .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests));
+
+ final int totalAugmentedRequests = mAugmentedRequestsLogs == null ? 0
+ : mAugmentedRequestsLogs.size();
+ if (totalAugmentedRequests > 0) {
+ if (sVerbose) {
+ Slog.v(TAG, "destroyLocked(): logging " + totalRequests + " augmented requests");
+ }
+ for (int i = 0; i < totalAugmentedRequests; i++) {
+ final LogMaker log = mAugmentedRequestsLogs.get(i);
+ mMetricsLogger.write(log);
+ }
+ }
+
+ final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests);
+ if (totalAugmentedRequests > 0) {
+ log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS,
+ totalAugmentedRequests);
+ }
+ mMetricsLogger.write(log);
return mRemoteFillService;
}
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 4a1e5b9..2241569 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -164,7 +164,7 @@
int N = pkgs.size();
for (int a = N-1; a >= 0; a--) {
PackageInfo pkg = pkgs.get(a);
- if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)) {
+ if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, userId)) {
pkgs.remove(a);
}
}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index b9a6f3c..4f58d79 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -636,14 +636,18 @@
}
@Override
- public void opComplete(int token, long result) throws RemoteException {
- int userId = binderGetCallingUserId();
+ public void opCompleteForUser(int userId, int token, long result) throws RemoteException {
if (isUserReadyForBackup(userId)) {
- mService.opComplete(binderGetCallingUserId(), token, result);
+ mService.opComplete(userId, token, result);
}
}
@Override
+ public void opComplete(int token, long result) throws RemoteException {
+ opCompleteForUser(binderGetCallingUserId(), token, result);
+ }
+
+ @Override
public long getAvailableRestoreTokenForUser(int userId, String packageName) {
return isUserReadyForBackup(userId) ? mService.getAvailableRestoreToken(userId,
packageName) : 0;
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 79f8a7e..115e924 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -804,7 +804,7 @@
public BackupAgent makeMetadataAgent() {
PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager, mUserId);
pmAgent.attach(mContext);
- pmAgent.onCreate();
+ pmAgent.onCreate(UserHandle.of(mUserId));
return pmAgent;
}
@@ -815,7 +815,7 @@
PackageManagerBackupAgent pmAgent =
new PackageManagerBackupAgent(mPackageManager, packages, mUserId);
pmAgent.attach(mContext);
- pmAgent.onCreate();
+ pmAgent.onCreate(UserHandle.of(mUserId));
return pmAgent;
}
@@ -910,10 +910,10 @@
long lastBackup = in.readLong();
foundApps.add(pkgName); // all apps that we've addressed already
try {
- PackageInfo pkg = mPackageManager.getPackageInfo(pkgName, 0);
+ PackageInfo pkg = mPackageManager.getPackageInfoAsUser(pkgName, 0, mUserId);
if (AppBackupUtils.appGetsFullBackup(pkg)
- && AppBackupUtils.appIsEligibleForBackup(
- pkg.applicationInfo, mPackageManager)) {
+ && AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo,
+ mUserId)) {
schedule.add(new FullBackupEntry(pkgName, lastBackup));
} else {
if (DEBUG) {
@@ -933,8 +933,8 @@
// scan to make sure that we're tracking all full-backup candidates properly
for (PackageInfo app : apps) {
if (AppBackupUtils.appGetsFullBackup(app)
- && AppBackupUtils.appIsEligibleForBackup(
- app.applicationInfo, mPackageManager)) {
+ && AppBackupUtils.appIsEligibleForBackup(app.applicationInfo,
+ mUserId)) {
if (!foundApps.contains(app.packageName)) {
if (MORE_DEBUG) {
Slog.i(TAG, "New full backup app " + app.packageName + " found");
@@ -960,7 +960,7 @@
schedule = new ArrayList<>(apps.size());
for (PackageInfo info : apps) {
if (AppBackupUtils.appGetsFullBackup(info) && AppBackupUtils.appIsEligibleForBackup(
- info.applicationInfo, mPackageManager)) {
+ info.applicationInfo, mUserId)) {
schedule.add(new FullBackupEntry(info.packageName, 0));
}
}
@@ -1222,8 +1222,8 @@
mPackageManager.getPackageInfoAsUser(
packageName, /* flags */ 0, mUserId);
if (AppBackupUtils.appGetsFullBackup(app)
- && AppBackupUtils.appIsEligibleForBackup(
- app.applicationInfo, mPackageManager)) {
+ && AppBackupUtils.appIsEligibleForBackup(app.applicationInfo,
+ mUserId)) {
enqueueFullBackup(packageName, now);
scheduleNextFullBackupJob(0);
} else {
@@ -1618,8 +1618,7 @@
try {
PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageName,
PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
- if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo,
- mPackageManager)) {
+ if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo, mUserId)) {
BackupObserverUtils.sendBackupOnPackageResult(observer, packageName,
BackupManager.ERROR_BACKUP_NOT_ALLOWED);
continue;
@@ -2095,7 +2094,8 @@
}
try {
- PackageInfo appInfo = mPackageManager.getPackageInfo(entry.packageName, 0);
+ PackageInfo appInfo = mPackageManager.getPackageInfoAsUser(
+ entry.packageName, 0, mUserId);
if (!AppBackupUtils.appGetsFullBackup(appInfo)) {
// The head app isn't supposed to get full-data backups [any more];
// so we cull it and force a loop around to consider the new head
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index 31786d7..0a7159b 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -286,7 +286,8 @@
Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
while (iter.hasNext()) {
PackageInfo pkg = iter.next().getValue();
- if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)
+ if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo,
+ mUserBackupManagerService.getUserId())
|| AppBackupUtils.appIsStopped(pkg.applicationInfo)) {
iter.remove();
if (DEBUG) {
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 0fb4f93..86e679f 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -143,6 +143,7 @@
private final int mBackupRunnerOpToken;
private final OnTaskFinishedListener mListener;
private final TransportClient mTransportClient;
+ private final int mUserId;
// This is true when a backup operation for some package is in progress.
private volatile boolean mIsDoingBackup;
@@ -173,6 +174,7 @@
mAgentTimeoutParameters = Preconditions.checkNotNull(
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
+ mUserId = backupManagerService.getUserId();
if (backupManagerService.isBackupOperationInProgress()) {
if (DEBUG) {
@@ -187,9 +189,10 @@
for (String pkg : whichPackages) {
try {
PackageManager pm = backupManagerService.getPackageManager();
- PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNING_CERTIFICATES);
+ PackageInfo info = pm.getPackageInfoAsUser(pkg,
+ PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
mCurrentPackage = info;
- if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) {
+ if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, mUserId)) {
// Cull any packages that have indicated that backups are not permitted,
// that run as system-domain uids but do not define their own backup agents,
// as well as any explicit mention of the 'special' shared-storage agent
@@ -633,7 +636,7 @@
unregisterTask();
if (mJob != null) {
- mJob.finishBackupPass(backupManagerService.getUserId());
+ mJob.finishBackupPass(mUserId);
}
synchronized (backupManagerService.getQueueLock()) {
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index cfc129e..294eb01 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -489,7 +489,7 @@
throw AgentException.permanent(e);
}
ApplicationInfo applicationInfo = packageInfo.applicationInfo;
- if (!AppBackupUtils.appIsEligibleForBackup(applicationInfo, mPackageManager)) {
+ if (!AppBackupUtils.appIsEligibleForBackup(applicationInfo, mUserId)) {
mReporter.onPackageNotEligibleForBackup(packageName);
throw AgentException.permanent();
}
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index b3d9fbc..c5389fa 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -230,7 +230,7 @@
PackageManagerInternal.class);
RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
mBackupManagerService.getPackageManager(), allowApks, info, signatures,
- pmi);
+ pmi, mUserId);
mManifestSignatures.put(info.packageName, signatures);
mPackagePolicies.put(pkg, restorePolicy);
mPackageInstallers.put(pkg, info.installerPackageName);
@@ -332,8 +332,9 @@
}
try {
- mTargetApp = mBackupManagerService.getPackageManager()
- .getApplicationInfoAsUser(pkg, 0, mUserId);
+ mTargetApp =
+ mBackupManagerService.getPackageManager()
+ .getApplicationInfoAsUser(pkg, 0, mUserId);
// If we haven't sent any data to this app yet, we probably
// need to clear it first. Check that.
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index d01f77b..7763d7b 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -232,7 +232,7 @@
continue;
}
- if (AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) {
+ if (AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, mUserId)) {
mAcceptSet.add(info);
}
} catch (NameNotFoundException e) {
diff --git a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
index 054879b..2db8928 100644
--- a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
@@ -18,19 +18,25 @@
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
+import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
import android.annotation.Nullable;
+import android.app.AppGlobals;
import android.app.backup.BackupTransport;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.content.pm.SigningInfo;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
import com.android.internal.util.ArrayUtils;
import com.android.server.backup.transport.TransportClient;
@@ -39,7 +45,6 @@
* Utility methods wrapping operations on ApplicationInfo and PackageInfo.
*/
public class AppBackupUtils {
-
private static final boolean DEBUG = false;
/**
@@ -54,15 +59,30 @@
* <li>it is the special shared-storage backup package used for 'adb backup'
* </ol>
*/
- public static boolean appIsEligibleForBackup(ApplicationInfo app, PackageManager pm) {
+ public static boolean appIsEligibleForBackup(ApplicationInfo app, int userId) {
+ return appIsEligibleForBackup(app, AppGlobals.getPackageManager(), userId);
+ }
+
+ @VisibleForTesting
+ static boolean appIsEligibleForBackup(ApplicationInfo app,
+ IPackageManager packageManager, int userId) {
// 1. their manifest states android:allowBackup="false"
if ((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) {
return false;
}
- // 2. they run as a system-level uid but do not supply their own backup agent
- if ((app.uid < Process.FIRST_APPLICATION_UID) && (app.backupAgentName == null)) {
- return false;
+ // 2. they run as a system-level uid
+ if ((app.uid < Process.FIRST_APPLICATION_UID)) {
+ // and the backup is happening for non-system user
+ if (userId != UserHandle.USER_SYSTEM && !app.packageName.equals(
+ PACKAGE_MANAGER_SENTINEL)) {
+ return false;
+ }
+
+ // or do not supply their own backup agent
+ if (app.backupAgentName == null) {
+ return false;
+ }
}
// 3. it is the special shared-storage backup package used for 'adb backup'
@@ -75,9 +95,7 @@
return false;
}
- // Everything else checks out; the only remaining roadblock would be if the
- // package were disabled
- return !appIsDisabled(app, pm);
+ return !appIsDisabled(app, packageManager, userId);
}
/**
@@ -99,9 +117,9 @@
PackageInfo packageInfo = pm.getPackageInfoAsUser(packageName,
PackageManager.GET_SIGNING_CERTIFICATES, userId);
ApplicationInfo applicationInfo = packageInfo.applicationInfo;
- if (!appIsEligibleForBackup(applicationInfo, pm)
+ if (!appIsEligibleForBackup(applicationInfo, userId)
|| appIsStopped(applicationInfo)
- || appIsDisabled(applicationInfo, pm)) {
+ || appIsDisabled(applicationInfo, userId)) {
return false;
}
if (transportClient != null) {
@@ -123,8 +141,22 @@
}
/** Avoid backups of 'disabled' apps. */
- public static boolean appIsDisabled(ApplicationInfo app, PackageManager pm) {
- switch (pm.getApplicationEnabledSetting(app.packageName)) {
+ static boolean appIsDisabled(ApplicationInfo app, int userId) {
+ return appIsDisabled(app, AppGlobals.getPackageManager(), userId);
+ }
+
+ @VisibleForTesting
+ static boolean appIsDisabled(ApplicationInfo app,
+ IPackageManager packageManager, int userId) {
+ int enabledSetting;
+ try {
+ enabledSetting = packageManager.getApplicationEnabledSetting(app.packageName, userId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get application enabled setting: " + e);
+ return false;
+ }
+
+ switch (enabledSetting) {
case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index 0f4b681..f4b235a 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -383,11 +383,12 @@
* @param allowApks - allow restore set to include apks.
* @param info - file metadata.
* @param signatures - array of signatures parsed from backup file.
+ * @param userId - ID of the user for which restore is performed.
* @return a restore policy constant.
*/
public RestorePolicy chooseRestorePolicy(PackageManager packageManager,
boolean allowApks, FileMetadata info, Signature[] signatures,
- PackageManagerInternal pmi) {
+ PackageManagerInternal pmi, int userId) {
if (signatures == null) {
return RestorePolicy.IGNORE;
}
@@ -396,8 +397,8 @@
// Okay, got the manifest info we need...
try {
- PackageInfo pkgInfo = packageManager.getPackageInfo(
- info.packageName, PackageManager.GET_SIGNING_CERTIFICATES);
+ PackageInfo pkgInfo = packageManager.getPackageInfoAsUser(
+ info.packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId);
// Fall through to IGNORE if the app explicitly disallows backup
final int flags = pkgInfo.applicationInfo.flags;
if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index e4bbcd6..844096d 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -35,6 +35,7 @@
import android.util.LocalLog;
import android.util.Slog;
import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.UserDataRemovalRequest;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
@@ -224,6 +225,15 @@
}
@Override
+ public void removeUserData(@UserIdInt int userId, @NonNull UserDataRemovalRequest request) {
+ Preconditions.checkNotNull(request);
+ synchronized (mLock) {
+ final ContentCapturePerUserService service = getServiceForUserLocked(userId);
+ service.removeUserDataLocked(request);
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 8d2c79b..bc0e19a 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -28,6 +28,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
@@ -35,18 +36,22 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.service.contentcapture.ContentCaptureService;
import android.service.contentcapture.IContentCaptureServiceCallback;
import android.service.contentcapture.SnapshotData;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
+import android.view.contentcapture.UserDataRemovalRequest;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
+import com.android.server.LocalServices;
import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks;
import com.android.server.infra.AbstractPerUserSystemService;
@@ -249,6 +254,39 @@
}
@GuardedBy("mLock")
+ public void removeUserDataLocked(@NonNull UserDataRemovalRequest request) {
+ if (!isEnabledLocked()) {
+ return;
+ }
+ assertCallerLocked(request.getPackageName());
+ mRemoteService.onUserDataRemovalRequest(request);
+ }
+
+ /**
+ * Asserts the component is owned by the caller.
+ */
+ @GuardedBy("mLock")
+ private void assertCallerLocked(@NonNull String packageName) {
+ final PackageManager pm = getContext().getPackageManager();
+ final int callingUid = Binder.getCallingUid();
+ final int packageUid;
+ try {
+ packageUid = pm.getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
+ } catch (NameNotFoundException e) {
+ throw new SecurityException("Could not verify UID for " + packageName);
+ }
+ if (callingUid != packageUid && !LocalServices.getService(ActivityManagerInternal.class)
+ .hasRunningActivity(callingUid, packageName)) {
+ final String[] packages = pm.getPackagesForUid(callingUid);
+ final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid;
+ Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid
+ + ") passed package (" + packageName + ") owned by UID " + packageUid);
+
+ throw new SecurityException("Invalid package: " + packageName);
+ }
+ }
+
+ @GuardedBy("mLock")
public boolean sendActivityAssistDataLocked(@NonNull IBinder activityToken,
@NonNull Bundle data) {
final String id = getSessionId(activityToken);
diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
index 12742ca..54eea5d 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
@@ -26,6 +26,7 @@
import android.text.format.DateUtils;
import android.util.Slog;
import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.UserDataRemovalRequest;
import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
import com.android.internal.os.IResultReceiver;
@@ -108,6 +109,13 @@
scheduleAsyncRequest((s) -> s.onActivitySnapshot(sessionId, snapshotData));
}
+ /**
+ * Called by {@link ContentCaptureServerSession} to request removal of user data.
+ */
+ public void onUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
+ scheduleAsyncRequest((s) -> s.onUserDataRemovalRequest(request));
+ }
+
public interface ContentCaptureServiceCallbacks
extends VultureCallback<RemoteContentCaptureService> {
// NOTE: so far we don't need to notify the callback implementation
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 0fa996e..e3dcb7d 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -64,6 +64,7 @@
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.ThreadLocalWorkSource;
import android.os.Trace;
import android.os.UserHandle;
import android.os.WorkSource;
@@ -76,6 +77,7 @@
import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Log;
+import android.util.LongArrayQueue;
import android.util.NtpTrustedTime;
import android.util.Pair;
import android.util.Slog;
@@ -90,6 +92,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.LocalLog;
import com.android.internal.util.StatLogger;
import com.android.server.AppStateTracker.Listener;
@@ -144,6 +147,7 @@
static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
static final int TICK_HISTORY_DEPTH = 10;
+ static final long MILLIS_IN_DAY = 24 * 60 * 60 * 1000;
// Indices into the APP_STANDBY_MIN_DELAYS and KEYS_APP_STANDBY_DELAY arrays
static final int ACTIVE_INDEX = 0;
@@ -194,6 +198,7 @@
ArrayList<Alarm> mPendingNonWakeupAlarms = new ArrayList<>();
ArrayList<InFlight> mInFlight = new ArrayList<>();
AlarmHandler mHandler;
+ AppWakeupHistory mAppWakeupHistory;
ClockReceiver mClockReceiver;
final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
Intent mTimeTickIntent;
@@ -276,7 +281,91 @@
private AppStateTracker mAppStateTracker;
private boolean mAppStandbyParole;
- private ArrayMap<Pair<String, Integer>, Long> mLastAlarmDeliveredForPackage = new ArrayMap<>();
+
+ /**
+ * A rolling window history of previous times when an alarm was sent to a package.
+ */
+ private static class AppWakeupHistory {
+ private ArrayMap<Pair<String, Integer>, LongArrayQueue> mPackageHistory =
+ new ArrayMap<>();
+ private long mWindowSize;
+
+ AppWakeupHistory(long windowSize) {
+ mWindowSize = windowSize;
+ }
+
+ void recordAlarmForPackage(String packageName, int userId, long nowElapsed) {
+ final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
+ LongArrayQueue history = mPackageHistory.get(packageUser);
+ if (history == null) {
+ history = new LongArrayQueue();
+ mPackageHistory.put(packageUser, history);
+ }
+ if (history.size() == 0 || history.peekLast() < nowElapsed) {
+ history.addLast(nowElapsed);
+ }
+ snapToWindow(history);
+ }
+
+ void removeForUser(int userId) {
+ for (int i = mPackageHistory.size() - 1; i >= 0; i--) {
+ final Pair<String, Integer> packageUserKey = mPackageHistory.keyAt(i);
+ if (packageUserKey.second == userId) {
+ mPackageHistory.removeAt(i);
+ }
+ }
+ }
+
+ void removeForPackage(String packageName, int userId) {
+ final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
+ mPackageHistory.remove(packageUser);
+ }
+
+ private void snapToWindow(LongArrayQueue history) {
+ while (history.peekFirst() + mWindowSize < history.peekLast()) {
+ history.removeFirst();
+ }
+ }
+
+ int getTotalWakeupsInWindow(String packageName, int userId) {
+ final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId));
+ return (history == null) ? 0 : history.size();
+ }
+
+ long getLastWakeupForPackage(String packageName, int userId, int positionFromEnd) {
+ final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId));
+ if (history == null) {
+ return 0;
+ }
+ final int i = history.size() - positionFromEnd;
+ return (i < 0) ? 0 : history.get(i);
+ }
+
+ void dump(PrintWriter pw, String prefix, long nowElapsed) {
+ dump(new IndentingPrintWriter(pw, " ").setIndent(prefix), nowElapsed);
+ }
+
+ void dump(IndentingPrintWriter pw, long nowElapsed) {
+ pw.println("App Alarm history:");
+ pw.increaseIndent();
+ for (int i = 0; i < mPackageHistory.size(); i++) {
+ final Pair<String, Integer> packageUser = mPackageHistory.keyAt(i);
+ final LongArrayQueue timestamps = mPackageHistory.valueAt(i);
+ pw.print(packageUser.first);
+ pw.print(", u");
+ pw.print(packageUser.second);
+ pw.print(": ");
+ // limit dumping to a max of 100 values
+ final int lastIdx = Math.max(0, timestamps.size() - 100);
+ for (int j = timestamps.size() - 1; j >= lastIdx; j--) {
+ TimeUtils.formatDuration(timestamps.get(j), nowElapsed, pw);
+ pw.print(", ");
+ }
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+ }
/**
* All times are in milliseconds. These constants are kept synchronized with the system
@@ -301,6 +390,17 @@
= "allow_while_idle_whitelist_duration";
@VisibleForTesting
static final String KEY_LISTENER_TIMEOUT = "listener_timeout";
+ @VisibleForTesting
+ static final String KEY_APP_STANDBY_QUOTAS_ENABLED = "app_standby_quotas_enabled";
+ private static final String KEY_APP_STANDBY_WINDOW = "app_standby_window";
+ @VisibleForTesting
+ final String[] KEYS_APP_STANDBY_QUOTAS = {
+ "standby_active_quota",
+ "standby_working_quota",
+ "standby_frequent_quota",
+ "standby_rare_quota",
+ "standby_never_quota",
+ };
// Keys for specifying throttling delay based on app standby bucketing
private final String[] KEYS_APP_STANDBY_DELAY = {
@@ -318,6 +418,18 @@
private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9*60*1000;
private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10*1000;
private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000;
+ private static final boolean DEFAULT_APP_STANDBY_QUOTAS_ENABLED = true;
+ private static final long DEFAULT_APP_STANDBY_WINDOW = 60 * 60 * 1000; // 1 hr
+ /**
+ * Max number of times an app can receive alarms in {@link #APP_STANDBY_WINDOW}
+ */
+ private final int[] DEFAULT_APP_STANDBY_QUOTAS = {
+ 720, // Active
+ 10, // Working
+ 2, // Frequent
+ 1, // Rare
+ 0 // Never
+ };
private final long[] DEFAULT_APP_STANDBY_DELAYS = {
0, // Active
6 * 60_000, // Working
@@ -347,8 +459,11 @@
// Direct alarm listener callback timeout
public long LISTENER_TIMEOUT = DEFAULT_LISTENER_TIMEOUT;
+ public boolean APP_STANDBY_QUOTAS_ENABLED = DEFAULT_APP_STANDBY_QUOTAS_ENABLED;
+ public long APP_STANDBY_WINDOW = DEFAULT_APP_STANDBY_WINDOW;
public long[] APP_STANDBY_MIN_DELAYS = new long[DEFAULT_APP_STANDBY_DELAYS.length];
+ public int[] APP_STANDBY_QUOTAS = new int[DEFAULT_APP_STANDBY_QUOTAS.length];
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -408,48 +523,90 @@
DEFAULT_APP_STANDBY_DELAYS[ACTIVE_INDEX]);
for (int i = WORKING_INDEX; i < KEYS_APP_STANDBY_DELAY.length; i++) {
APP_STANDBY_MIN_DELAYS[i] = mParser.getDurationMillis(KEYS_APP_STANDBY_DELAY[i],
- Math.max(APP_STANDBY_MIN_DELAYS[i-1], DEFAULT_APP_STANDBY_DELAYS[i]));
+ Math.max(APP_STANDBY_MIN_DELAYS[i - 1], DEFAULT_APP_STANDBY_DELAYS[i]));
+ }
+
+ APP_STANDBY_QUOTAS_ENABLED = mParser.getBoolean(KEY_APP_STANDBY_QUOTAS_ENABLED,
+ DEFAULT_APP_STANDBY_QUOTAS_ENABLED);
+
+ APP_STANDBY_WINDOW = mParser.getLong(KEY_APP_STANDBY_WINDOW,
+ DEFAULT_APP_STANDBY_WINDOW);
+ if (APP_STANDBY_WINDOW > DEFAULT_APP_STANDBY_WINDOW) {
+ Slog.w(TAG, "Cannot exceed the app_standby_window size of "
+ + DEFAULT_APP_STANDBY_WINDOW);
+ APP_STANDBY_WINDOW = DEFAULT_APP_STANDBY_WINDOW;
+ } else if (APP_STANDBY_WINDOW < DEFAULT_APP_STANDBY_WINDOW) {
+ // Not recommended outside of testing.
+ Slog.w(TAG, "Using a non-default app_standby_window of " + APP_STANDBY_WINDOW);
+ }
+
+ APP_STANDBY_QUOTAS[ACTIVE_INDEX] = mParser.getInt(
+ KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX],
+ DEFAULT_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
+ for (int i = WORKING_INDEX; i < KEYS_APP_STANDBY_QUOTAS.length; i++) {
+ APP_STANDBY_QUOTAS[i] = mParser.getInt(KEYS_APP_STANDBY_QUOTAS[i],
+ Math.min(APP_STANDBY_QUOTAS[i - 1], DEFAULT_APP_STANDBY_QUOTAS[i]));
}
updateAllowWhileIdleWhitelistDurationLocked();
}
}
- void dump(PrintWriter pw) {
- pw.println(" Settings:");
+ void dump(PrintWriter pw, String prefix) {
+ dump(new IndentingPrintWriter(pw, " ").setIndent(prefix));
+ }
- pw.print(" "); pw.print(KEY_MIN_FUTURITY); pw.print("=");
+ void dump(IndentingPrintWriter pw) {
+ pw.println("Settings:");
+
+ pw.increaseIndent();
+
+ pw.print(KEY_MIN_FUTURITY); pw.print("=");
TimeUtils.formatDuration(MIN_FUTURITY, pw);
pw.println();
- pw.print(" "); pw.print(KEY_MIN_INTERVAL); pw.print("=");
+ pw.print(KEY_MIN_INTERVAL); pw.print("=");
TimeUtils.formatDuration(MIN_INTERVAL, pw);
pw.println();
- pw.print(" "); pw.print(KEY_MAX_INTERVAL); pw.print("=");
+ pw.print(KEY_MAX_INTERVAL); pw.print("=");
TimeUtils.formatDuration(MAX_INTERVAL, pw);
pw.println();
- pw.print(" "); pw.print(KEY_LISTENER_TIMEOUT); pw.print("=");
+ pw.print(KEY_LISTENER_TIMEOUT); pw.print("=");
TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
pw.println();
- pw.print(" "); pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); pw.print("=");
+ pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); pw.print("=");
TimeUtils.formatDuration(ALLOW_WHILE_IDLE_SHORT_TIME, pw);
pw.println();
- pw.print(" "); pw.print(KEY_ALLOW_WHILE_IDLE_LONG_TIME); pw.print("=");
+ pw.print(KEY_ALLOW_WHILE_IDLE_LONG_TIME); pw.print("=");
TimeUtils.formatDuration(ALLOW_WHILE_IDLE_LONG_TIME, pw);
pw.println();
- pw.print(" "); pw.print(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION); pw.print("=");
+ pw.print(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION); pw.print("=");
TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw);
pw.println();
for (int i = 0; i < KEYS_APP_STANDBY_DELAY.length; i++) {
- pw.print(" "); pw.print(KEYS_APP_STANDBY_DELAY[i]); pw.print("=");
+ pw.print(KEYS_APP_STANDBY_DELAY[i]); pw.print("=");
TimeUtils.formatDuration(APP_STANDBY_MIN_DELAYS[i], pw);
pw.println();
}
+
+ pw.print(KEY_APP_STANDBY_QUOTAS_ENABLED); pw.print("=");
+ pw.println(APP_STANDBY_QUOTAS_ENABLED);
+
+ pw.print(KEY_APP_STANDBY_WINDOW); pw.print("=");
+ TimeUtils.formatDuration(APP_STANDBY_WINDOW, pw);
+ pw.println();
+
+ for (int i = 0; i < KEYS_APP_STANDBY_QUOTAS.length; i++) {
+ pw.print(KEYS_APP_STANDBY_QUOTAS[i]); pw.print("=");
+ pw.println(APP_STANDBY_QUOTAS[i]);
+ }
+
+ pw.decreaseIndent();
}
void dumpProto(ProtoOutputStream proto, long fieldId) {
@@ -924,7 +1081,7 @@
if (targetPackages != null && !targetPackages.contains(packageUser)) {
continue;
}
- if (adjustDeliveryTimeBasedOnStandbyBucketLocked(alarm)) {
+ if (adjustDeliveryTimeBasedOnBucketLocked(alarm)) {
batch.remove(alarm);
rescheduledAlarms.add(alarm);
}
@@ -1131,23 +1288,23 @@
final IBinder mListener;
final WorkSource mWorkSource;
final int mUid;
+ final int mCreatorUid;
final String mTag;
final BroadcastStats mBroadcastStats;
final FilterStats mFilterStats;
final int mAlarmType;
- InFlight(AlarmManagerService service, PendingIntent pendingIntent, IAlarmListener listener,
- WorkSource workSource, int uid, String alarmPkg, int alarmType, String tag,
- long nowELAPSED) {
- mPendingIntent = pendingIntent;
+ InFlight(AlarmManagerService service, Alarm alarm, long nowELAPSED) {
+ mPendingIntent = alarm.operation;
mWhenElapsed = nowELAPSED;
- mListener = listener != null ? listener.asBinder() : null;
- mWorkSource = workSource;
- mUid = uid;
- mTag = tag;
- mBroadcastStats = (pendingIntent != null)
- ? service.getStatsLocked(pendingIntent)
- : service.getStatsLocked(uid, alarmPkg);
+ mListener = alarm.listener != null ? alarm.listener.asBinder() : null;
+ mWorkSource = alarm.workSource;
+ mUid = alarm.uid;
+ mCreatorUid = alarm.creatorUid;
+ mTag = alarm.statsTag;
+ mBroadcastStats = (alarm.operation != null)
+ ? service.getStatsLocked(alarm.operation)
+ : service.getStatsLocked(alarm.uid, alarm.packageName);
FilterStats fs = mBroadcastStats.filterStats.get(mTag);
if (fs == null) {
fs = new FilterStats(mBroadcastStats, mTag);
@@ -1155,7 +1312,7 @@
}
fs.lastTime = nowELAPSED;
mFilterStats = fs;
- mAlarmType = alarmType;
+ mAlarmType = alarm.type;
}
@Override
@@ -1165,6 +1322,7 @@
+ ", when=" + mWhenElapsed
+ ", workSource=" + mWorkSource
+ ", uid=" + mUid
+ + ", creatorUid=" + mCreatorUid
+ ", tag=" + mTag
+ ", broadcastStats=" + mBroadcastStats
+ ", filterStats=" + mFilterStats
@@ -1298,6 +1456,7 @@
synchronized (mLock) {
mHandler = new AlarmHandler();
mConstants = new Constants(mHandler);
+ mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
mNextWakeup = mNextNonWakeup = 0;
@@ -1581,6 +1740,27 @@
}
/**
+ * Returns the maximum alarms that an app in the specified bucket can receive in a rolling time
+ * window given by {@link Constants#APP_STANDBY_WINDOW}
+ */
+ @VisibleForTesting
+ int getQuotaForBucketLocked(int bucket) {
+ final int index;
+ if (bucket <= UsageStatsManager.STANDBY_BUCKET_ACTIVE) {
+ index = ACTIVE_INDEX;
+ } else if (bucket <= UsageStatsManager.STANDBY_BUCKET_WORKING_SET) {
+ index = WORKING_INDEX;
+ } else if (bucket <= UsageStatsManager.STANDBY_BUCKET_FREQUENT) {
+ index = FREQUENT_INDEX;
+ } else if (bucket < UsageStatsManager.STANDBY_BUCKET_NEVER) {
+ index = RARE_INDEX;
+ } else {
+ index = NEVER_INDEX;
+ }
+ return mConstants.APP_STANDBY_QUOTAS[index];
+ }
+
+ /**
* Return the minimum time that should elapse before an app in the specified bucket
* can receive alarms again
*/
@@ -1606,7 +1786,7 @@
* @param alarm The alarm to adjust
* @return true if the alarm delivery time was updated.
*/
- private boolean adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) {
+ private boolean adjustDeliveryTimeBasedOnBucketLocked(Alarm alarm) {
if (isExemptFromAppStandby(alarm)) {
return false;
}
@@ -1627,18 +1807,49 @@
final int standbyBucket = mUsageStatsManagerInternal.getAppStandbyBucket(
sourcePackage, sourceUserId, mInjector.getElapsedRealtime());
- final Pair<String, Integer> packageUser = Pair.create(sourcePackage, sourceUserId);
- final long lastElapsed = mLastAlarmDeliveredForPackage.getOrDefault(packageUser, 0L);
- if (lastElapsed > 0) {
- final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket);
- if (alarm.expectedWhenElapsed < minElapsed) {
- alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
- } else {
- // app is now eligible to run alarms at the originally requested window.
+ if (mConstants.APP_STANDBY_QUOTAS_ENABLED) {
+ // Quota deferring implementation:
+ final int wakeupsInWindow = mAppWakeupHistory.getTotalWakeupsInWindow(sourcePackage,
+ sourceUserId);
+ final int quotaForBucket = getQuotaForBucketLocked(standbyBucket);
+ boolean deferred = false;
+ if (wakeupsInWindow >= quotaForBucket) {
+ final long minElapsed;
+ if (quotaForBucket <= 0) {
+ // Just keep deferring for a day till the quota changes
+ minElapsed = mInjector.getElapsedRealtime() + MILLIS_IN_DAY;
+ } else {
+ // Suppose the quota for window was q, and the qth last delivery time for this
+ // package was t(q) then the next delivery must be after t(q) + <window_size>
+ final long t = mAppWakeupHistory.getLastWakeupForPackage(sourcePackage,
+ sourceUserId, quotaForBucket);
+ minElapsed = t + 1 + mConstants.APP_STANDBY_WINDOW;
+ }
+ if (alarm.expectedWhenElapsed < minElapsed) {
+ alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
+ deferred = true;
+ }
+ }
+ if (!deferred) {
// Restore original requirements in case they were changed earlier.
alarm.whenElapsed = alarm.expectedWhenElapsed;
alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
}
+ } else {
+ // Minimum delay deferring implementation:
+ final long lastElapsed = mAppWakeupHistory.getLastWakeupForPackage(sourcePackage,
+ sourceUserId, 1);
+ if (lastElapsed > 0) {
+ final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket);
+ if (alarm.expectedWhenElapsed < minElapsed) {
+ alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
+ } else {
+ // app is now eligible to run alarms at the originally requested window.
+ // Restore original requirements in case they were changed earlier.
+ alarm.whenElapsed = alarm.expectedWhenElapsed;
+ alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
+ }
+ }
}
return (oldWhenElapsed != alarm.whenElapsed || oldMaxWhenElapsed != alarm.maxWhenElapsed);
}
@@ -1694,7 +1905,7 @@
mAllowWhileIdleDispatches.add(ent);
}
}
- adjustDeliveryTimeBasedOnStandbyBucketLocked(a);
+ adjustDeliveryTimeBasedOnBucketLocked(a);
insertAndBatchAlarmLocked(a);
if (a.alarmClock != null) {
@@ -1913,7 +2124,7 @@
void dumpImpl(PrintWriter pw) {
synchronized (mLock) {
pw.println("Current Alarm Manager state:");
- mConstants.dump(pw);
+ mConstants.dump(pw, " ");
pw.println();
if (mAppStateTracker != null) {
@@ -2063,14 +2274,7 @@
pw.println(" none");
}
- pw.println(" mLastAlarmDeliveredForPackage:");
- for (int i = 0; i < mLastAlarmDeliveredForPackage.size(); i++) {
- Pair<String, Integer> packageUser = mLastAlarmDeliveredForPackage.keyAt(i);
- pw.print(" Package " + packageUser.first + ", User " + packageUser.second + ":");
- TimeUtils.formatDuration(mLastAlarmDeliveredForPackage.valueAt(i), nowELAPSED, pw);
- pw.println();
- }
- pw.println();
+ mAppWakeupHistory.dump(pw, " ", nowELAPSED);
if (mPendingIdleUntil != null || mPendingWhileIdleAlarms.size() > 0) {
pw.println();
@@ -3811,12 +4015,10 @@
/**
* Attribute blame for a WakeLock.
- * @param pi PendingIntent to attribute blame to if ws is null.
* @param ws WorkSource to attribute blame.
- * @param knownUid attribution uid; < 0 if we need to derive it from the PendingIntent sender
+ * @param knownUid attribution uid; < 0 values are ignored.
*/
- void setWakelockWorkSource(PendingIntent pi, WorkSource ws, int type, String tag,
- int knownUid, boolean first) {
+ void setWakelockWorkSource(WorkSource ws, int knownUid, String tag, boolean first) {
try {
mWakeLock.setHistoryTag(first ? tag : null);
@@ -3825,11 +4027,8 @@
return;
}
- final int uid = (knownUid >= 0)
- ? knownUid
- : ActivityManager.getService().getUidForIntentSender(pi.getTarget());
- if (uid >= 0) {
- mWakeLock.setWorkSource(new WorkSource(uid));
+ if (knownUid >= 0) {
+ mWakeLock.setWorkSource(new WorkSource(knownUid));
return;
}
} catch (Exception e) {
@@ -3839,6 +4038,14 @@
mWakeLock.setWorkSource(null);
}
+ private static int getAlarmAttributionUid(Alarm alarm) {
+ if (alarm.workSource != null && !alarm.workSource.isEmpty()) {
+ return alarm.workSource.getAttributionUid();
+ }
+
+ return alarm.creatorUid;
+ }
+
@VisibleForTesting
class AlarmHandler extends Handler {
public static final int ALARM_EVENT = 1;
@@ -3857,6 +4064,7 @@
obtainMessage(REMOVE_FOR_STOPPED, uid, 0).sendToTarget();
}
+ @Override
public void handleMessage(Message msg) {
switch (msg.what) {
case ALARM_EVENT: {
@@ -4025,64 +4233,57 @@
public void onReceive(Context context, Intent intent) {
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
synchronized (mLock) {
- String action = intent.getAction();
String pkgList[] = null;
- if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
- for (String packageName : pkgList) {
- if (lookForPackageLocked(packageName)) {
- setResultCode(Activity.RESULT_OK);
- return;
- }
- }
- return;
- } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
- int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (userHandle >= 0) {
- removeUserLocked(userHandle);
- for (int i = mLastAlarmDeliveredForPackage.size() - 1; i >= 0; i--) {
- final Pair<String, Integer> packageUser =
- mLastAlarmDeliveredForPackage.keyAt(i);
- if (packageUser.second == userHandle) {
- mLastAlarmDeliveredForPackage.removeAt(i);
+ switch (intent.getAction()) {
+ case Intent.ACTION_QUERY_PACKAGE_RESTART:
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+ for (String packageName : pkgList) {
+ if (lookForPackageLocked(packageName)) {
+ setResultCode(Activity.RESULT_OK);
+ return;
}
}
- }
- } else if (Intent.ACTION_UID_REMOVED.equals(action)) {
- if (uid >= 0) {
- mLastAllowWhileIdleDispatch.delete(uid);
- mUseAllowWhileIdleShortTime.delete(uid);
- }
- } else {
- if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
- && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
- // This package is being updated; don't kill its alarms.
return;
- }
- Uri data = intent.getData();
- if (data != null) {
- String pkg = data.getSchemeSpecificPart();
- if (pkg != null) {
- pkgList = new String[]{pkg};
+ case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ break;
+ case Intent.ACTION_USER_STOPPED:
+ final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userHandle >= 0) {
+ removeUserLocked(userHandle);
+ mAppWakeupHistory.removeForUser(userHandle);
}
- }
+ return;
+ case Intent.ACTION_UID_REMOVED:
+ if (uid >= 0) {
+ mLastAllowWhileIdleDispatch.delete(uid);
+ mUseAllowWhileIdleShortTime.delete(uid);
+ }
+ return;
+ case Intent.ACTION_PACKAGE_REMOVED:
+ if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ // This package is being updated; don't kill its alarms.
+ return;
+ }
+ // Intentional fall-through.
+ case Intent.ACTION_PACKAGE_RESTARTED:
+ final Uri data = intent.getData();
+ if (data != null) {
+ final String pkg = data.getSchemeSpecificPart();
+ if (pkg != null) {
+ pkgList = new String[]{pkg};
+ }
+ }
+ break;
}
if (pkgList != null && (pkgList.length > 0)) {
- for (int i = mLastAlarmDeliveredForPackage.size() - 1; i >= 0; i--) {
- Pair<String, Integer> packageUser = mLastAlarmDeliveredForPackage.keyAt(i);
- if (ArrayUtils.contains(pkgList, packageUser.first)
- && packageUser.second == UserHandle.getUserId(uid)) {
- mLastAlarmDeliveredForPackage.removeAt(i);
- }
- }
for (String pkg : pkgList) {
if (uid >= 0) {
- // package-removed case
+ // package-removed and package-restarted case
+ mAppWakeupHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
removeLocked(uid);
} else {
- // external-applications-unavailable etc case
+ // external-applications-unavailable case
removeLocked(pkg);
}
mPriorities.remove(pkg);
@@ -4126,7 +4327,8 @@
/**
* Tracking of app assignments to standby buckets
*/
- final class AppStandbyTracker extends UsageStatsManagerInternal.AppIdleStateChangeListener {
+ private final class AppStandbyTracker extends
+ UsageStatsManagerInternal.AppIdleStateChangeListener {
@Override
public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
boolean idle, int bucket, int reason) {
@@ -4285,8 +4487,8 @@
// the next of our alarms is now in flight. reattribute the wakelock.
if (mInFlight.size() > 0) {
InFlight inFlight = mInFlight.get(0);
- setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource,
- inFlight.mAlarmType, inFlight.mTag, -1, false);
+ setWakelockWorkSource(inFlight.mWorkSource, inFlight.mCreatorUid, inFlight.mTag,
+ false);
} else {
// should never happen
mLog.w("Alarm wakelock still held but sent queue empty");
@@ -4369,64 +4571,70 @@
*/
@GuardedBy("mLock")
public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
- if (alarm.operation != null) {
- // PendingIntent alarm
- mSendCount++;
+ final long workSourceToken = ThreadLocalWorkSource.setUid(
+ getAlarmAttributionUid(alarm));
+ try {
+ if (alarm.operation != null) {
+ // PendingIntent alarm
+ mSendCount++;
- try {
- alarm.operation.send(getContext(), 0,
- mBackgroundIntent.putExtra(
- Intent.EXTRA_ALARM_COUNT, alarm.count),
- mDeliveryTracker, mHandler, null,
- allowWhileIdle ? mIdleOptions : null);
- } catch (PendingIntent.CanceledException e) {
- if (alarm.repeatInterval > 0) {
- // This IntentSender is no longer valid, but this
- // is a repeating alarm, so toss it
- removeImpl(alarm.operation, null);
+ try {
+ alarm.operation.send(getContext(), 0,
+ mBackgroundIntent.putExtra(
+ Intent.EXTRA_ALARM_COUNT, alarm.count),
+ mDeliveryTracker, mHandler, null,
+ allowWhileIdle ? mIdleOptions : null);
+ } catch (PendingIntent.CanceledException e) {
+ if (alarm.repeatInterval > 0) {
+ // This IntentSender is no longer valid, but this
+ // is a repeating alarm, so toss it
+ removeImpl(alarm.operation, null);
+ }
+ // No actual delivery was possible, so the delivery tracker's
+ // 'finished' callback won't be invoked. We also don't need
+ // to do any wakelock or stats tracking, so we have nothing
+ // left to do here but go on to the next thing.
+ mSendFinishCount++;
+ return;
}
- // No actual delivery was possible, so the delivery tracker's
- // 'finished' callback won't be invoked. We also don't need
- // to do any wakelock or stats tracking, so we have nothing
- // left to do here but go on to the next thing.
- mSendFinishCount++;
- return;
- }
- } else {
- // Direct listener callback alarm
- mListenerCount++;
+ } else {
+ // Direct listener callback alarm
+ mListenerCount++;
- if (RECORD_ALARMS_IN_HISTORY) {
- if (alarm.listener == mTimeTickTrigger) {
- mTickHistory[mNextTickHistory++] = nowELAPSED;
- if (mNextTickHistory >= TICK_HISTORY_DEPTH) {
- mNextTickHistory = 0;
+ if (RECORD_ALARMS_IN_HISTORY) {
+ if (alarm.listener == mTimeTickTrigger) {
+ mTickHistory[mNextTickHistory++] = nowELAPSED;
+ if (mNextTickHistory >= TICK_HISTORY_DEPTH) {
+ mNextTickHistory = 0;
+ }
}
}
- }
- try {
- if (DEBUG_LISTENER_CALLBACK) {
- Slog.v(TAG, "Alarm to uid=" + alarm.uid
- + " listener=" + alarm.listener.asBinder());
+ try {
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.v(TAG, "Alarm to uid=" + alarm.uid
+ + " listener=" + alarm.listener.asBinder());
+ }
+ alarm.listener.doAlarm(this);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(AlarmHandler.LISTENER_TIMEOUT,
+ alarm.listener.asBinder()),
+ mConstants.LISTENER_TIMEOUT);
+ } catch (Exception e) {
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.i(TAG, "Alarm undeliverable to listener "
+ + alarm.listener.asBinder(), e);
+ }
+ // As in the PendingIntent.CanceledException case, delivery of the
+ // alarm was not possible, so we have no wakelock or timeout or
+ // stats management to do. It threw before we posted the delayed
+ // timeout message, so we're done here.
+ mListenerFinishCount++;
+ return;
}
- alarm.listener.doAlarm(this);
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(AlarmHandler.LISTENER_TIMEOUT,
- alarm.listener.asBinder()),
- mConstants.LISTENER_TIMEOUT);
- } catch (Exception e) {
- if (DEBUG_LISTENER_CALLBACK) {
- Slog.i(TAG, "Alarm undeliverable to listener "
- + alarm.listener.asBinder(), e);
- }
- // As in the PendingIntent.CanceledException case, delivery of the
- // alarm was not possible, so we have no wakelock or timeout or
- // stats management to do. It threw before we posted the delayed
- // timeout message, so we're done here.
- mListenerFinishCount++;
- return;
}
+ } finally {
+ ThreadLocalWorkSource.restore(workSourceToken);
}
// The alarm is now in flight; now arrange wakelock and stats tracking
@@ -4434,15 +4642,11 @@
Slog.d(TAG, "mBroadcastRefCount -> " + (mBroadcastRefCount + 1));
}
if (mBroadcastRefCount == 0) {
- setWakelockWorkSource(alarm.operation, alarm.workSource,
- alarm.type, alarm.statsTag, (alarm.operation == null) ? alarm.uid : -1,
- true);
+ setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true);
mWakeLock.acquire();
mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1).sendToTarget();
}
- final InFlight inflight = new InFlight(AlarmManagerService.this,
- alarm.operation, alarm.listener, alarm.workSource, alarm.uid,
- alarm.packageName, alarm.type, alarm.statsTag, nowELAPSED);
+ final InFlight inflight = new InFlight(AlarmManagerService.this, alarm, nowELAPSED);
mInFlight.add(inflight);
mBroadcastRefCount++;
if (allowWhileIdle) {
@@ -4467,7 +4671,8 @@
if (!isExemptFromAppStandby(alarm)) {
final Pair<String, Integer> packageUser = Pair.create(alarm.sourcePackage,
UserHandle.getUserId(alarm.creatorUid));
- mLastAlarmDeliveredForPackage.put(packageUser, nowELAPSED);
+ mAppWakeupHistory.recordAlarmForPackage(alarm.sourcePackage,
+ UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
}
final BroadcastStats bs = inflight.mBroadcastStats;
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index a333381..c4bc52c 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -208,6 +208,8 @@
private int mErrorRecoveryRetryCounter;
private final int mSystemUiUid;
+ private boolean mIsHearingAidProfileSupported;
+
// Save a ProfileServiceConnections object for each of the bound
// bluetooth profile services
private final Map<Integer, ProfileServiceConnections> mProfileServices = new HashMap<>();
@@ -391,13 +393,19 @@
mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>();
mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>();
+ mIsHearingAidProfileSupported = context.getResources()
+ .getBoolean(com.android.internal.R.bool.config_hearing_aid_profile_supported);
+
// TODO: We need a more generic way to initialize the persist keys of FeatureFlagUtils
- boolean isHearingAidEnabled;
String value = SystemProperties.get(FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.HEARING_AID_SETTINGS);
if (!TextUtils.isEmpty(value)) {
- isHearingAidEnabled = Boolean.parseBoolean(value);
+ boolean isHearingAidEnabled = Boolean.parseBoolean(value);
Log.v(TAG, "set feature flag HEARING_AID_SETTINGS to " + isHearingAidEnabled);
FeatureFlagUtils.setEnabled(context, FeatureFlagUtils.HEARING_AID_SETTINGS, isHearingAidEnabled);
+ if (isHearingAidEnabled && !mIsHearingAidProfileSupported) {
+ // Overwrite to enable support by FeatureFlag
+ mIsHearingAidProfileSupported = true;
+ }
}
IntentFilter filter = new IntentFilter();
@@ -679,6 +687,11 @@
return false;
}
+ @Override
+ public boolean isHearingAidProfileSupported() {
+ return mIsHearingAidProfileSupported;
+ }
+
// Monitor change of BLE scan only mode settings.
private void registerForBleScanModeChange() {
ContentObserver contentObserver = new ContentObserver(null) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 00550d9..14e2354 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -73,6 +73,7 @@
import android.net.LinkProperties;
import android.net.LinkProperties.CompareResult;
import android.net.MatchAllNetworkSpecifier;
+import android.net.NattSocketKeepalive;
import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
@@ -97,10 +98,10 @@
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.netlink.InetDiagMessage;
+import android.net.shared.NetdService;
import android.net.shared.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
-import android.net.util.NetdService;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -237,6 +238,9 @@
// connect anyway?" dialog after the user selects a network that doesn't validate.
private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;
+ // How long to dismiss network notification.
+ private static final int TIMEOUT_NOTIFICATION_DELAY_MS = 20 * 1000;
+
// Default to 30s linger time-out. Modifiable only for testing.
private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
@@ -473,6 +477,11 @@
public static final int EVENT_PROVISIONING_NOTIFICATION = 43;
/**
+ * This event can handle dismissing notification by given network id.
+ */
+ public static final int EVENT_TIMEOUT_NOTIFICATION = 44;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -506,7 +515,8 @@
// A helper object to track the current default HTTP proxy. ConnectivityService needs to tell
// the world when it changes.
- private final ProxyTracker mProxyTracker;
+ @VisibleForTesting
+ protected final ProxyTracker mProxyTracker;
final private SettingsObserver mSettingsObserver;
@@ -815,7 +825,7 @@
mPolicyManagerInternal = checkNotNull(
LocalServices.getService(NetworkPolicyManagerInternal.class),
"missing NetworkPolicyManagerInternal");
- mProxyTracker = new ProxyTracker(context, mHandler, EVENT_PROXY_HAS_CHANGED);
+ mProxyTracker = makeProxyTracker();
mNetd = NetdService.getInstance();
mKeyStore = KeyStore.getInstance();
@@ -981,6 +991,11 @@
deps);
}
+ @VisibleForTesting
+ protected ProxyTracker makeProxyTracker() {
+ return new ProxyTracker(mContext, mHandler, EVENT_PROXY_HAS_CHANGED);
+ }
+
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
@@ -2476,6 +2491,11 @@
final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID);
final boolean wasValidated = nai.lastValidated;
final boolean wasDefault = isDefaultNetwork(nai);
+ if (nai.everCaptivePortalDetected && !nai.captivePortalLoginNotified
+ && valid) {
+ nai.captivePortalLoginNotified = true;
+ showNetworkNotification(nai, NotificationType.LOGGED_IN);
+ }
final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : "";
@@ -2496,7 +2516,15 @@
updateCapabilities(oldScore, nai, nai.networkCapabilities);
// If score has changed, rebroadcast to NetworkFactories. b/17726566
if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
- if (valid) handleFreshlyValidatedNetwork(nai);
+ if (valid) {
+ handleFreshlyValidatedNetwork(nai);
+ // Clear NO_INTERNET and LOST_INTERNET notifications if network becomes
+ // valid.
+ mNotifier.clearNotification(nai.network.netId,
+ NotificationType.NO_INTERNET);
+ mNotifier.clearNotification(nai.network.netId,
+ NotificationType.LOST_INTERNET);
+ }
}
updateInetCondition(nai);
// Let the NetworkAgent know the state of its network
@@ -2520,6 +2548,9 @@
final int oldScore = nai.getCurrentScore();
nai.lastCaptivePortalDetected = visible;
nai.everCaptivePortalDetected |= visible;
+ if (visible) {
+ nai.captivePortalLoginNotified = false;
+ }
if (nai.lastCaptivePortalDetected &&
Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) {
if (DBG) log("Avoiding captive portal network: " + nai.name());
@@ -2531,7 +2562,10 @@
updateCapabilities(oldScore, nai, nai.networkCapabilities);
}
if (!visible) {
- mNotifier.clearNotification(netId);
+ // Only clear SIGN_IN and NETWORK_SWITCH notifications here, or else other
+ // notifications belong to the same network may be cleared unexpected.
+ mNotifier.clearNotification(netId, NotificationType.SIGN_IN);
+ mNotifier.clearNotification(netId, NotificationType.NETWORK_SWITCH);
} else {
if (nai == null) {
loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
@@ -2640,8 +2674,7 @@
}
private boolean networkRequiresValidation(NetworkAgentInfo nai) {
- return isValidationRequired(
- mDefaultRequest.networkCapabilities, nai.networkCapabilities);
+ return isValidationRequired(nai.networkCapabilities);
}
private void handleFreshlyValidatedNetwork(NetworkAgentInfo nai) {
@@ -3183,6 +3216,15 @@
return mMultinetworkPolicyTracker.getAvoidBadWifi();
}
+ @Override
+ public boolean getAvoidBadWifi() {
+ if (!checkNetworkStackPermission()) {
+ throw new SecurityException("avoidBadWifi requires NETWORK_STACK permission");
+ }
+ return avoidBadWifi();
+ }
+
+
private void rematchForAvoidBadWifiUpdate() {
rematchAllNetworksAndRequests(null, 0);
for (NetworkAgentInfo nai: mNetworkAgentInfos.values()) {
@@ -3229,9 +3271,15 @@
pw.decreaseIndent();
}
- private void showValidationNotification(NetworkAgentInfo nai, NotificationType type) {
+ private void showNetworkNotification(NetworkAgentInfo nai, NotificationType type) {
final String action;
switch (type) {
+ case LOGGED_IN:
+ action = Settings.ACTION_WIFI_SETTINGS;
+ mHandler.removeMessages(EVENT_TIMEOUT_NOTIFICATION);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NOTIFICATION,
+ nai.network.netId, 0), TIMEOUT_NOTIFICATION_DELAY_MS);
+ break;
case NO_INTERNET:
action = ConnectivityManager.ACTION_PROMPT_UNVALIDATED;
break;
@@ -3244,10 +3292,12 @@
}
Intent intent = new Intent(action);
- intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setClassName("com.android.settings",
- "com.android.settings.wifi.WifiNoInternetDialog");
+ if (type != NotificationType.LOGGED_IN) {
+ intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClassName("com.android.settings",
+ "com.android.settings.wifi.WifiNoInternetDialog");
+ }
PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
@@ -3265,7 +3315,7 @@
!nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
return;
}
- showValidationNotification(nai, NotificationType.NO_INTERNET);
+ showNetworkNotification(nai, NotificationType.NO_INTERNET);
}
private void handleNetworkUnvalidated(NetworkAgentInfo nai) {
@@ -3274,7 +3324,7 @@
if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) {
- showValidationNotification(nai, NotificationType.LOST_INTERNET);
+ showNetworkNotification(nai, NotificationType.LOST_INTERNET);
}
}
@@ -3420,6 +3470,9 @@
case EVENT_DATA_SAVER_CHANGED:
handleRestrictBackgroundChanged(toBool(msg.arg1));
break;
+ case EVENT_TIMEOUT_NOTIFICATION:
+ mNotifier.clearNotification(msg.arg1, NotificationType.LOGGED_IN);
+ break;
}
}
}
@@ -3677,20 +3730,46 @@
}
}
+ /**
+ * Returns information about the proxy a certain network is using. If given a null network, it
+ * it will return the proxy for the bound network for the caller app or the default proxy if
+ * none.
+ *
+ * @param network the network we want to get the proxy information for.
+ * @return Proxy information if a network has a proxy configured, or otherwise null.
+ */
@Override
public ProxyInfo getProxyForNetwork(Network network) {
- if (network == null) return mProxyTracker.getDefaultProxy();
final ProxyInfo globalProxy = mProxyTracker.getGlobalProxy();
if (globalProxy != null) return globalProxy;
- if (!NetworkUtils.queryUserAccess(Binder.getCallingUid(), network.netId)) return null;
- // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
- // caller may not have.
+ if (network == null) {
+ // Get the network associated with the calling UID.
+ final Network activeNetwork = getActiveNetworkForUidInternal(Binder.getCallingUid(),
+ true);
+ if (activeNetwork == null) {
+ return null;
+ }
+ return getLinkPropertiesProxyInfo(activeNetwork);
+ } else if (queryUserAccess(Binder.getCallingUid(), network.netId)) {
+ // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
+ // caller may not have.
+ return getLinkPropertiesProxyInfo(network);
+ }
+ // No proxy info available if the calling UID does not have network access.
+ return null;
+ }
+
+ @VisibleForTesting
+ protected boolean queryUserAccess(int uid, int netId) {
+ return NetworkUtils.queryUserAccess(uid, netId);
+ }
+
+ private ProxyInfo getLinkPropertiesProxyInfo(Network network) {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null) return null;
synchronized (nai) {
- final ProxyInfo proxyInfo = nai.linkProperties.getHttpProxy();
- if (proxyInfo == null) return null;
- return new ProxyInfo(proxyInfo);
+ final ProxyInfo linkHttpProxy = nai.linkProperties.getHttpProxy();
+ return linkHttpProxy == null ? null : new ProxyInfo(linkHttpProxy);
}
}
@@ -3714,11 +3793,10 @@
mProxyTracker.setDefaultProxy(proxy);
}
- // If the proxy has changed from oldLp to newLp, resend proxy broadcast with default proxy.
- // This method gets called when any network changes proxy, but the broadcast only ever contains
- // the default proxy (even if it hasn't changed).
- // TODO: Deprecate the broadcast extras as they aren't necessarily applicable in a multi-network
- // world where an app might be bound to a non-default network.
+ // If the proxy has changed from oldLp to newLp, resend proxy broadcast. This method gets called
+ // when any network changes proxy.
+ // TODO: Remove usage of broadcast extras as they are deprecated and not applicable in a
+ // multi-network world where an app might be bound to a non-default network.
private void updateProxy(LinkProperties newLp, LinkProperties oldLp) {
ProxyInfo newProxyInfo = newLp == null ? null : newLp.getHttpProxy();
ProxyInfo oldProxyInfo = oldLp == null ? null : oldLp.getHttpProxy();
@@ -5885,12 +5963,6 @@
}
scheduleUnvalidatedPrompt(networkAgent);
- if (networkAgent.isVPN()) {
- // Temporarily disable the default proxy (not global).
- mProxyTracker.setDefaultProxyEnabled(false);
- // TODO: support proxy per network.
- }
-
// Whether a particular NetworkRequest listen should cause signal strength thresholds to
// be communicated to a particular NetworkAgent depends only on the network's immutable,
// capabilities, so it only needs to be done once on initial connect, not every time the
@@ -5909,10 +5981,16 @@
} else if (state == NetworkInfo.State.DISCONNECTED) {
networkAgent.asyncChannel.disconnect();
if (networkAgent.isVPN()) {
- mProxyTracker.setDefaultProxyEnabled(true);
updateUids(networkAgent, networkAgent.networkCapabilities, null);
}
disconnectAndDestroyNetwork(networkAgent);
+ if (networkAgent.isVPN()) {
+ // As the active or bound network changes for apps, broadcast the default proxy, as
+ // apps may need to update their proxy data. This is called after disconnecting from
+ // VPN to make sure we do not broadcast the old proxy data.
+ // TODO(b/122649188): send the broadcast only to VPN users.
+ mProxyTracker.sendProxyBroadcast();
+ }
} else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) ||
state == NetworkInfo.State.SUSPENDED) {
// going into or coming out of SUSPEND: re-score and notify
@@ -6177,6 +6255,17 @@
}
@Override
+ public void startNattKeepaliveWithFd(Network network, FileDescriptor fd, int resourceId,
+ int intervalSeconds, Messenger messenger, IBinder binder, String srcAddr,
+ String dstAddr) {
+ enforceKeepalivePermission();
+ mKeepaliveTracker.startNattKeepalive(
+ getNetworkAgentInfoForNetwork(network), fd, resourceId,
+ intervalSeconds, messenger, binder,
+ srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT);
+ }
+
+ @Override
public void stopKeepalive(Network network, int slot) {
mHandler.sendMessage(mHandler.obtainMessage(
NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, slot, PacketKeepalive.SUCCESS, network));
@@ -6346,6 +6435,20 @@
}
}
+ @GuardedBy("mVpns")
+ private Vpn getVpnIfOwner() {
+ final int uid = Binder.getCallingUid();
+ final int user = UserHandle.getUserId(uid);
+
+ final Vpn vpn = mVpns.get(user);
+ if (vpn == null) {
+ return null;
+ } else {
+ final VpnInfo info = vpn.getVpnInfo();
+ return (info == null || info.ownerUid != uid) ? null : vpn;
+ }
+ }
+
/**
* Caller either needs to be an active VPN, or hold the NETWORK_STACK permission
* for testing.
@@ -6354,14 +6457,10 @@
if (checkNetworkStackPermission()) {
return null;
}
- final int uid = Binder.getCallingUid();
- final int user = UserHandle.getUserId(uid);
synchronized (mVpns) {
- Vpn vpn = mVpns.get(user);
- try {
- if (vpn.getVpnInfo().ownerUid == uid) return vpn;
- } catch (NullPointerException e) {
- /* vpn is null, or VPN is not connected and getVpnInfo() is null. */
+ Vpn vpn = getVpnIfOwner();
+ if (vpn != null) {
+ return vpn;
}
}
throw new SecurityException("App must either be an active VPN or have the NETWORK_STACK "
@@ -6390,4 +6489,20 @@
return uid;
}
+
+ @Override
+ public boolean isCallerCurrentAlwaysOnVpnApp() {
+ synchronized (mVpns) {
+ Vpn vpn = getVpnIfOwner();
+ return vpn != null && vpn.getAlwaysOn();
+ }
+ }
+
+ @Override
+ public boolean isCallerCurrentAlwaysOnVpnLockdownApp() {
+ synchronized (mVpns) {
+ Vpn vpn = getVpnIfOwner();
+ return vpn != null && vpn.getLockdown();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 121a830..39030aa 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -341,6 +341,29 @@
@VisibleForTesting
static final int STATE_QUICK_DOZE_DELAY = 7;
+ private static final int ACTIVE_REASON_UNKNOWN = 0;
+ private static final int ACTIVE_REASON_MOTION = 1;
+ private static final int ACTIVE_REASON_SCREEN = 2;
+ private static final int ACTIVE_REASON_CHARGING = 3;
+ private static final int ACTIVE_REASON_UNLOCKED = 4;
+ private static final int ACTIVE_REASON_FROM_BINDER_CALL = 5;
+ private static final int ACTIVE_REASON_FORCED = 6;
+ private static final int ACTIVE_REASON_ALARM = 7;
+ @VisibleForTesting
+ static final int SET_IDLE_FACTOR_RESULT_UNINIT = -1;
+ @VisibleForTesting
+ static final int SET_IDLE_FACTOR_RESULT_IGNORED = 0;
+ @VisibleForTesting
+ static final int SET_IDLE_FACTOR_RESULT_OK = 1;
+ @VisibleForTesting
+ static final int SET_IDLE_FACTOR_RESULT_NOT_SUPPORT = 2;
+ @VisibleForTesting
+ static final int SET_IDLE_FACTOR_RESULT_INVALID = 3;
+ @VisibleForTesting
+ static final long MIN_STATE_STEP_ALARM_CHANGE = 60 * 1000;
+ @VisibleForTesting
+ static final float MIN_PRE_IDLE_FACTOR_CHANGE = 0.05f;
+
@VisibleForTesting
static String stateToString(int state) {
switch (state) {
@@ -405,6 +428,7 @@
private long mNextSensingTimeoutAlarmTime;
private long mCurIdleBudget;
private long mMaintenanceStartTime;
+ private long mIdleStartTime;
private int mActiveIdleOpCount;
private PowerManager.WakeLock mActiveIdleWakeLock; // held when there are operations in progress
@@ -415,6 +439,17 @@
private boolean mAlarmsActive;
private boolean mReportedMaintenanceActivity;
+ /* Factor to apply to INACTIVE_TIMEOUT and IDLE_AFTER_INACTIVE_TIMEOUT in order to enter
+ * STATE_IDLE faster or slower. Don't apply this to SENSING_TIMEOUT or LOCATING_TIMEOUT because:
+ * - Both of them are shorter
+ * - Device sensor might take time be to become be stabilized
+ * Also don't apply the factor if the device is in motion because device motion provides a
+ * stronger signal than a prediction algorithm.
+ */
+ private float mPreIdleFactor;
+ private float mLastPreIdleFactor;
+ private int mActiveReason;
+
public final AtomicFile mConfigFile;
private final RemoteCallbackList<IMaintenanceActivityListener> mMaintenanceActivityListeners =
@@ -760,6 +795,10 @@
* exit doze. Default = true
*/
private static final String KEY_WAIT_FOR_UNLOCK = "wait_for_unlock";
+ private static final String KEY_PRE_IDLE_FACTOR_LONG =
+ "pre_idle_factor_long";
+ private static final String KEY_PRE_IDLE_FACTOR_SHORT =
+ "pre_idle_factor_short";
/**
* This is the time, after becoming inactive, that we go in to the first
@@ -987,6 +1026,16 @@
*/
public long NOTIFICATION_WHITELIST_DURATION;
+ /**
+ * Pre idle time factor use to make idle delay longer
+ */
+ public float PRE_IDLE_FACTOR_LONG;
+
+ /**
+ * Pre idle time factor use to make idle delay shorter
+ */
+ public float PRE_IDLE_FACTOR_SHORT;
+
public boolean WAIT_FOR_UNLOCK;
private final ContentResolver mResolver;
@@ -1082,6 +1131,8 @@
NOTIFICATION_WHITELIST_DURATION = mParser.getDurationMillis(
KEY_NOTIFICATION_WHITELIST_DURATION, 30 * 1000L);
WAIT_FOR_UNLOCK = mParser.getBoolean(KEY_WAIT_FOR_UNLOCK, false);
+ PRE_IDLE_FACTOR_LONG = mParser.getFloat(KEY_PRE_IDLE_FACTOR_LONG, 1.67f);
+ PRE_IDLE_FACTOR_SHORT = mParser.getFloat(KEY_PRE_IDLE_FACTOR_SHORT, 0.33f);
}
}
@@ -1196,6 +1247,12 @@
pw.print(" "); pw.print(KEY_WAIT_FOR_UNLOCK); pw.print("=");
pw.println(WAIT_FOR_UNLOCK);
+
+ pw.print(" "); pw.print(KEY_PRE_IDLE_FACTOR_LONG); pw.print("=");
+ pw.println(PRE_IDLE_FACTOR_LONG);
+
+ pw.print(" "); pw.print(KEY_PRE_IDLE_FACTOR_SHORT); pw.print("=");
+ pw.println(PRE_IDLE_FACTOR_SHORT);
}
}
@@ -1244,6 +1301,8 @@
private static final int MSG_FINISH_IDLE_OP = 8;
private static final int MSG_REPORT_TEMP_APP_WHITELIST_CHANGED = 9;
private static final int MSG_SEND_CONSTRAINT_MONITORING = 10;
+ private static final int MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR = 11;
+ private static final int MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR = 12;
final class MyHandler extends Handler {
MyHandler(Looper looper) {
@@ -1373,6 +1432,13 @@
constraint.stopMonitoring();
}
} break;
+ case MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR: {
+ updatePreIdleFactor();
+ } break;
+ case MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR: {
+ updatePreIdleFactor();
+ maybeDoImmediateMaintenance();
+ } break;
}
}
}
@@ -1526,6 +1592,28 @@
DeviceIdleController.this.unregisterMaintenanceActivityListener(listener);
}
+ @Override public int setPreIdleTimeoutMode(int mode) {
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
+ null);
+ long ident = Binder.clearCallingIdentity();
+ try {
+ return DeviceIdleController.this.setPreIdleTimeoutMode(mode);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override public void resetPreIdleTimeoutMode() {
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
+ null);
+ long ident = Binder.clearCallingIdentity();
+ try {
+ DeviceIdleController.this.resetPreIdleTimeoutMode();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
@Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
DeviceIdleController.this.dump(fd, pw, args);
}
@@ -1768,9 +1856,12 @@
// Start out assuming we are charging. If we aren't, we will at least get
// a battery update the next time the level drops.
mCharging = true;
+ mActiveReason = ACTIVE_REASON_UNKNOWN;
mState = STATE_ACTIVE;
mLightState = LIGHT_STATE_ACTIVE;
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
+ mPreIdleFactor = 1.0f;
+ mLastPreIdleFactor = 1.0f;
}
mBinderService = new BinderService();
@@ -2394,6 +2485,7 @@
public void exitIdleInternal(String reason) {
synchronized (this) {
+ mActiveReason = ACTIVE_REASON_FROM_BINDER_CALL;
becomeActiveLocked(reason, Binder.getCallingUid());
}
}
@@ -2463,6 +2555,7 @@
} else if (screenOn) {
mScreenOn = true;
if (!mForceIdle && (!mScreenLocked || !mConstants.WAIT_FOR_UNLOCK)) {
+ mActiveReason = ACTIVE_REASON_SCREEN;
becomeActiveLocked("screen", Process.myUid());
}
}
@@ -2485,6 +2578,7 @@
} else if (charging) {
mCharging = charging;
if (!mForceIdle) {
+ mActiveReason = ACTIVE_REASON_CHARGING;
becomeActiveLocked("charging", Process.myUid());
}
}
@@ -2516,6 +2610,7 @@
if (mScreenLocked != showing) {
mScreenLocked = showing;
if (mScreenOn && !mForceIdle && !mScreenLocked) {
+ mActiveReason = ACTIVE_REASON_UNLOCKED;
becomeActiveLocked("unlocked", Process.myUid());
}
}
@@ -2587,7 +2682,11 @@
mState = STATE_INACTIVE;
if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
resetIdleManagementLocked();
- scheduleAlarmLocked(mInactiveTimeout, false);
+ long delay = mInactiveTimeout;
+ if (shouldUseIdleTimeoutFactorLocked()) {
+ delay = (long) (mPreIdleFactor * delay);
+ }
+ scheduleAlarmLocked(delay, false);
EventLogTags.writeDeviceIdle(mState, "no activity");
}
}
@@ -2605,6 +2704,7 @@
mNextIdlePendingDelay = 0;
mNextIdleDelay = 0;
mNextLightIdleDelay = 0;
+ mIdleStartTime = 0;
cancelAlarmLocked();
cancelSensingTimeoutAlarmLocked();
cancelLocatingLocked();
@@ -2621,6 +2721,7 @@
if (mForceIdle) {
mForceIdle = false;
if (mScreenOn || mCharging) {
+ mActiveReason = ACTIVE_REASON_FORCED;
becomeActiveLocked("exit-force", Process.myUid());
}
}
@@ -2740,6 +2841,7 @@
if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) {
// Whoops, there is an upcoming alarm. We don't actually want to go idle.
if (mState != STATE_ACTIVE) {
+ mActiveReason = ACTIVE_REASON_ALARM;
becomeActiveLocked("alarm", Process.myUid());
becomeInactiveIfAppropriateLocked();
}
@@ -2763,7 +2865,11 @@
// We have now been inactive long enough, it is time to start looking
// for motion and sleep some more while doing so.
startMonitoringMotionLocked();
- scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);
+ long delay = mConstants.IDLE_AFTER_INACTIVE_TIMEOUT;
+ if (shouldUseIdleTimeoutFactorLocked()) {
+ delay = (long) (mPreIdleFactor * delay);
+ }
+ scheduleAlarmLocked(delay, false);
moveToStateLocked(STATE_IDLE_PENDING, reason);
break;
case STATE_IDLE_PENDING:
@@ -2834,6 +2940,7 @@
" ms.");
mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
+ mIdleStartTime = SystemClock.elapsedRealtime();
mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
mNextIdleDelay = mConstants.IDLE_TIMEOUT;
@@ -2934,6 +3041,127 @@
}
}
+ @VisibleForTesting
+ int setPreIdleTimeoutMode(int mode) {
+ return setPreIdleTimeoutFactor(getPreIdleTimeoutByMode(mode));
+ }
+
+ @VisibleForTesting
+ float getPreIdleTimeoutByMode(int mode) {
+ switch (mode) {
+ case PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG: {
+ return mConstants.PRE_IDLE_FACTOR_LONG;
+ }
+ case PowerManager.PRE_IDLE_TIMEOUT_MODE_SHORT: {
+ return mConstants.PRE_IDLE_FACTOR_SHORT;
+ }
+ case PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL: {
+ return 1.0f;
+ }
+ default: {
+ Slog.w(TAG, "Invalid time out factor mode: " + mode);
+ return 1.0f;
+ }
+ }
+ }
+
+ @VisibleForTesting
+ float getPreIdleTimeoutFactor() {
+ return mPreIdleFactor;
+ }
+
+ @VisibleForTesting
+ int setPreIdleTimeoutFactor(float ratio) {
+ if (!mDeepEnabled) {
+ if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Deep Idle disable");
+ return SET_IDLE_FACTOR_RESULT_NOT_SUPPORT;
+ } else if (ratio <= MIN_PRE_IDLE_FACTOR_CHANGE) {
+ if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Invalid input");
+ return SET_IDLE_FACTOR_RESULT_INVALID;
+ } else if (Math.abs(ratio - mPreIdleFactor) < MIN_PRE_IDLE_FACTOR_CHANGE) {
+ if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: New factor same as previous factor");
+ return SET_IDLE_FACTOR_RESULT_IGNORED;
+ }
+ synchronized (this) {
+ mLastPreIdleFactor = mPreIdleFactor;
+ mPreIdleFactor = ratio;
+ }
+ if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: " + ratio);
+ postUpdatePreIdleFactor();
+ return SET_IDLE_FACTOR_RESULT_OK;
+ }
+
+ @VisibleForTesting
+ void resetPreIdleTimeoutMode() {
+ synchronized (this) {
+ mLastPreIdleFactor = mPreIdleFactor;
+ mPreIdleFactor = 1.0f;
+ }
+ if (DEBUG) Slog.d(TAG, "resetPreIdleTimeoutMode to 1.0");
+ postResetPreIdleTimeoutFactor();
+ }
+
+ private void postUpdatePreIdleFactor() {
+ mHandler.sendEmptyMessage(MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR);
+ }
+
+ private void postResetPreIdleTimeoutFactor() {
+ mHandler.sendEmptyMessage(MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR);
+ }
+
+ @VisibleForTesting
+ void updatePreIdleFactor() {
+ synchronized (this) {
+ if (!shouldUseIdleTimeoutFactorLocked()) {
+ return;
+ }
+ if (mState == STATE_INACTIVE || mState == STATE_IDLE_PENDING) {
+ if (mNextAlarmTime == 0) {
+ return;
+ }
+ long delay = mNextAlarmTime - SystemClock.elapsedRealtime();
+ if (delay < MIN_STATE_STEP_ALARM_CHANGE) {
+ return;
+ }
+ long newDelay = (long) (delay / mLastPreIdleFactor * mPreIdleFactor);
+ if (Math.abs(delay - newDelay) < MIN_STATE_STEP_ALARM_CHANGE) {
+ return;
+ }
+ scheduleAlarmLocked(newDelay, false);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void maybeDoImmediateMaintenance() {
+ synchronized (this) {
+ if (mState == STATE_IDLE) {
+ long duration = SystemClock.elapsedRealtime() - mIdleStartTime;
+ /* Let's trgger a immediate maintenance,
+ * if it has been idle for a long time */
+ if (duration > mConstants.IDLE_TIMEOUT) {
+ scheduleAlarmLocked(0, false);
+ }
+ }
+ }
+ }
+
+ private boolean shouldUseIdleTimeoutFactorLocked() {
+ // exclude ACTIVE_REASON_MOTION, for exclude device in pocket case
+ if (mActiveReason == ACTIVE_REASON_MOTION) {
+ return false;
+ }
+ return true;
+ }
+
+ /** Must only be used in tests. */
+ @VisibleForTesting
+ void setIdleStartTimeForTest(long idleStartTime) {
+ synchronized (this) {
+ mIdleStartTime = idleStartTime;
+ }
+ }
+
void reportMaintenanceActivityIfNeededLocked() {
boolean active = mJobsActive;
if (active == mReportedMaintenanceActivity) {
@@ -2945,6 +3173,11 @@
mHandler.sendMessage(msg);
}
+ @VisibleForTesting
+ long getNextAlarmTime() {
+ return mNextAlarmTime;
+ }
+
boolean isOpsInactiveLocked() {
return mActiveIdleOpCount <= 0 && !mJobsActive && !mAlarmsActive;
}
@@ -2994,6 +3227,7 @@
scheduleReportActiveLocked(type, Process.myUid());
addEvent(EVENT_NORMAL, type);
}
+ mActiveReason = ACTIVE_REASON_MOTION;
mState = STATE_ACTIVE;
mInactiveTimeout = timeout;
mCurIdleBudget = 0;
@@ -3401,6 +3635,11 @@
+ "and any [-d] is ignored");
pw.println(" motion");
pw.println(" Simulate a motion event to bring the device out of deep doze");
+ pw.println(" pre-idle-factor [0|1|2]");
+ pw.println(" Set a new factor to idle time before step to idle"
+ + "(inactive_to and idle_after_inactive_to)");
+ pw.println(" reset-pre-idle-factor");
+ pw.println(" Reset factor to idle time to default");
}
class Shell extends ShellCommand {
@@ -3571,6 +3810,7 @@
}
}
if (becomeActive) {
+ mActiveReason = ACTIVE_REASON_FORCED;
becomeActiveLocked((arg == null ? "all" : arg) + "-disabled",
Process.myUid());
}
@@ -3820,6 +4060,52 @@
Binder.restoreCallingIdentity(token);
}
}
+ } else if ("pre-idle-factor".equals(cmd)) {
+ getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+ null);
+ synchronized (this) {
+ long token = Binder.clearCallingIdentity();
+ int ret = SET_IDLE_FACTOR_RESULT_UNINIT;
+ try {
+ String arg = shell.getNextArg();
+ boolean valid = false;
+ int mode = 0;
+ if (arg != null) {
+ mode = Integer.parseInt(arg);
+ ret = setPreIdleTimeoutMode(mode);
+ if (ret == SET_IDLE_FACTOR_RESULT_OK) {
+ pw.println("pre-idle-factor: " + mode);
+ valid = true;
+ } else if (ret == SET_IDLE_FACTOR_RESULT_NOT_SUPPORT) {
+ valid = true;
+ pw.println("Deep idle not supported");
+ } else if (ret == SET_IDLE_FACTOR_RESULT_IGNORED) {
+ valid = true;
+ pw.println("Idle timeout factor not changed");
+ }
+ }
+ if (!valid) {
+ pw.println("Unknown idle timeout factor: " + arg
+ + ",(error code: " + ret + ")");
+ }
+ } catch (NumberFormatException e) {
+ pw.println("Unknown idle timeout factor"
+ + ",(error code: " + ret + ")");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } else if ("reset-pre-idle-factor".equals(cmd)) {
+ getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+ null);
+ synchronized (this) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ resetPreIdleTimeoutMode();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
} else {
return shell.handleDefaultCommands(cmd);
}
@@ -4053,6 +4339,9 @@
if (mAlarmsActive) {
pw.print(" mAlarmsActive="); pw.println(mAlarmsActive);
}
+ if (Math.abs(mPreIdleFactor - 1.0f) > MIN_PRE_IDLE_FACTOR_CHANGE) {
+ pw.print(" mPreIdleFactor="); pw.println(mPreIdleFactor);
+ }
}
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 126bf65..371276f 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -44,7 +44,7 @@
import android.net.Network;
import android.net.NetworkUtils;
import android.net.TrafficStats;
-import android.net.util.NetdService;
+import android.net.shared.NetdService;
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index add5e5f..d2c6354 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -46,6 +46,7 @@
import android.content.pm.Signature;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.hardware.location.ActivityRecognitionHardware;
import android.location.Address;
import android.location.Criteria;
import android.location.GeocoderParams;
@@ -66,7 +67,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
@@ -87,11 +87,11 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
-import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.location.AbstractLocationProvider;
+import com.android.server.location.ActivityRecognitionProxy;
import com.android.server.location.GeocoderProxy;
import com.android.server.location.GeofenceManager;
import com.android.server.location.GeofenceProxy;
@@ -220,6 +220,8 @@
private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>();
+ private final ArraySet<String> mIgnoreSettingsPackageWhitelist = new ArraySet<>();
+
@GuardedBy("mLock")
private final ArrayMap<IBinder, Identity> mGnssMeasurementsListeners = new ArrayMap<>();
@@ -246,7 +248,7 @@
public LocationManagerService(Context context) {
super();
mContext = context;
- mHandler = BackgroundThread.getHandler();
+ mHandler = FgThread.getHandler();
// Let the package manager query which are the default location
// providers as they get certain permissions granted by default.
@@ -353,6 +355,18 @@
}
}
}, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(
+ Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST),
+ true,
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ synchronized (mLock) {
+ onIgnoreSettingsWhitelistChangedLocked();
+ }
+ }
+ }, UserHandle.USER_ALL);
new PackageMonitor() {
@Override
@@ -550,6 +564,25 @@
}
}
+ @GuardedBy("lock")
+ private void onIgnoreSettingsWhitelistChangedLocked() {
+ String setting = Settings.Global.getString(
+ mContext.getContentResolver(),
+ Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST);
+ if (setting == null) {
+ setting = "";
+ }
+
+ mIgnoreSettingsPackageWhitelist.clear();
+ mIgnoreSettingsPackageWhitelist.addAll(
+ SystemConfig.getInstance().getAllowIgnoreLocationSettings());
+ mIgnoreSettingsPackageWhitelist.addAll(Arrays.asList(setting.split(",")));
+
+ for (LocationProvider p : mProviders) {
+ applyRequirementsLocked(p);
+ }
+ }
+
@GuardedBy("mLock")
private void onUserProfilesChangedLocked() {
mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId);
@@ -736,6 +769,25 @@
Slog.d(TAG, "Unable to bind FLP Geofence proxy.");
}
+ // bind to hardware activity recognition
+ boolean activityRecognitionHardwareIsSupported = ActivityRecognitionHardware.isSupported();
+ ActivityRecognitionHardware activityRecognitionHardware = null;
+ if (activityRecognitionHardwareIsSupported) {
+ activityRecognitionHardware = ActivityRecognitionHardware.getInstance(mContext);
+ } else {
+ Slog.d(TAG, "Hardware Activity-Recognition not supported.");
+ }
+ ActivityRecognitionProxy proxy = ActivityRecognitionProxy.createAndBind(
+ mContext,
+ activityRecognitionHardwareIsSupported,
+ activityRecognitionHardware,
+ com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay,
+ com.android.internal.R.string.config_activityRecognitionHardwarePackageName,
+ com.android.internal.R.array.config_locationProviderPackageNames);
+ if (proxy == null) {
+ Slog.d(TAG, "Unable to bind ActivityRecognitionProxy.");
+ }
+
String[] testProviderStrings = resources.getStringArray(
com.android.internal.R.array.config_testLocationProviders);
for (String testProviderString : testProviderStrings) {
@@ -830,6 +882,15 @@
mAllowed = !mIsManagedBySettings;
mEnabled = false;
mProperties = null;
+
+ if (mIsManagedBySettings) {
+ // since we assume providers are disabled by default
+ Settings.Secure.putStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ "-" + mName,
+ mCurrentUserId);
+ }
}
@GuardedBy("mLock")
@@ -954,7 +1015,7 @@
public void onReportLocation(Location location) {
// no security check necessary because this is coming from an internal-only interface
// move calls coming from below LMS onto a different thread to avoid deadlock
- runInternal(() -> {
+ mHandler.post(() -> {
synchronized (mLock) {
handleLocationChangedLocked(location, this);
}
@@ -965,7 +1026,7 @@
@Override
public void onReportLocation(List<Location> locations) {
// move calls coming from below LMS onto a different thread to avoid deadlock
- runInternal(() -> {
+ mHandler.post(() -> {
synchronized (mLock) {
LocationProvider gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
if (gpsProvider == null || !gpsProvider.isUseableLocked()) {
@@ -991,7 +1052,7 @@
@Override
public void onSetEnabled(boolean enabled) {
// move calls coming from below LMS onto a different thread to avoid deadlock
- runInternal(() -> {
+ mHandler.post(() -> {
synchronized (mLock) {
if (enabled == mEnabled) {
return;
@@ -1113,16 +1174,6 @@
mUseable = false;
updateProviderUseableLocked(this);
}
-
- // binder transactions coming from below LMS (ie location providers) need to be moved onto
- // a different thread to avoid potential deadlock as code reenters the location providers
- private void runInternal(Runnable runnable) {
- if (Looper.myLooper() == mHandler.getLooper()) {
- runnable.run();
- } else {
- mHandler.post(runnable);
- }
- }
}
private class MockLocationProvider extends LocationProvider {
@@ -1278,7 +1329,10 @@
// are high power (has a high power provider with an interval under a threshold).
for (UpdateRecord updateRecord : mUpdateRecords.values()) {
LocationProvider provider = getLocationProviderLocked(updateRecord.mProvider);
- if (provider == null || !provider.isUseableLocked()) {
+ if (provider == null) {
+ continue;
+ }
+ if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) {
continue;
}
@@ -1960,14 +2014,22 @@
ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
if (records != null) {
for (UpdateRecord record : records) {
- if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
- // Sends a notification message to the receiver
- if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
- if (deadReceivers == null) {
- deadReceivers = new ArrayList<>();
- }
- deadReceivers.add(record.mReceiver);
+ if (!isCurrentProfileLocked(
+ UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+ continue;
+ }
+
+ // requests that ignore location settings will never provider notifications
+ if (isSettingsExemptLocked(record)) {
+ continue;
+ }
+
+ // Sends a notification message to the receiver
+ if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
+ if (deadReceivers == null) {
+ deadReceivers = new ArrayList<>();
}
+ deadReceivers.add(record.mReceiver);
}
}
}
@@ -2007,39 +2069,46 @@
Binder.restoreCallingIdentity(identity);
}
- if (provider.isUseableLocked() && records != null && !records.isEmpty()) {
+ if (records != null && !records.isEmpty()) {
// initialize the low power mode to true and set to false if any of the records requires
providerRequest.lowPowerMode = true;
for (UpdateRecord record : records) {
- if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
- if (checkLocationAccess(
- record.mReceiver.mIdentity.mPid,
- record.mReceiver.mIdentity.mUid,
- record.mReceiver.mIdentity.mPackageName,
- record.mReceiver.mAllowedResolutionLevel)) {
- LocationRequest locationRequest = record.mRealRequest;
- long interval = locationRequest.getInterval();
+ if (!isCurrentProfileLocked(
+ UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+ continue;
+ }
+ if (!checkLocationAccess(
+ record.mReceiver.mIdentity.mPid,
+ record.mReceiver.mIdentity.mUid,
+ record.mReceiver.mIdentity.mPackageName,
+ record.mReceiver.mAllowedResolutionLevel)) {
+ continue;
+ }
+ if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) {
+ continue;
+ }
- if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
- if (!record.mIsForegroundUid) {
- interval = Math.max(interval, backgroundThrottleInterval);
- }
- if (interval != locationRequest.getInterval()) {
- locationRequest = new LocationRequest(locationRequest);
- locationRequest.setInterval(interval);
- }
- }
+ LocationRequest locationRequest = record.mRealRequest;
+ long interval = locationRequest.getInterval();
- record.mRequest = locationRequest;
- providerRequest.locationRequests.add(locationRequest);
- if (!locationRequest.isLowPowerMode()) {
- providerRequest.lowPowerMode = false;
- }
- if (interval < providerRequest.interval) {
- providerRequest.reportLocation = true;
- providerRequest.interval = interval;
- }
+ if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
+ if (!record.mIsForegroundUid) {
+ interval = Math.max(interval, backgroundThrottleInterval);
}
+ if (interval != locationRequest.getInterval()) {
+ locationRequest = new LocationRequest(locationRequest);
+ locationRequest.setInterval(interval);
+ }
+ }
+
+ record.mRequest = locationRequest;
+ providerRequest.locationRequests.add(locationRequest);
+ if (!locationRequest.isLowPowerMode()) {
+ providerRequest.lowPowerMode = false;
+ }
+ if (interval < providerRequest.interval) {
+ providerRequest.reportLocation = true;
+ providerRequest.interval = interval;
}
}
@@ -2125,6 +2194,25 @@
return false;
}
+ @GuardedBy("mLock")
+ private boolean isSettingsExemptLocked(UpdateRecord record) {
+ if (!record.mRealRequest.isLocationSettingsIgnored()) {
+ return false;
+ }
+
+ if (mBackgroundThrottlePackageWhitelist.contains(record.mReceiver.mIdentity.mPackageName)) {
+ return true;
+ }
+
+ for (LocationProvider provider : mProviders) {
+ if (record.mReceiver.mIdentity.mPackageName.equals(provider.getPackageLocked())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
private class UpdateRecord {
final String mProvider;
private final LocationRequest mRealRequest; // original request from client
@@ -2268,7 +2356,7 @@
}
// make getFastestInterval() the minimum of interval and fastest interval
if (sanitizedRequest.getFastestInterval() > sanitizedRequest.getInterval()) {
- request.setFastestInterval(request.getInterval());
+ sanitizedRequest.setFastestInterval(request.getInterval());
}
return sanitizedRequest;
}
@@ -2307,6 +2395,10 @@
mContext.enforceCallingOrSelfPermission(
Manifest.permission.UPDATE_APP_OPS_STATS, null);
}
+ if (request.isLocationSettingsIgnored()) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.WRITE_SECURE_SETTINGS, null);
+ }
boolean callerHasLocationHardwarePermission =
mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
== PERMISSION_GRANTED;
@@ -2376,12 +2468,14 @@
oldRecord.disposeLocked(false);
}
- if (provider.isUseableLocked()) {
- applyRequirementsLocked(name);
- } else {
- // Notify the listener that updates are currently disabled
+ if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) {
+ // Notify the listener that updates are currently disabled - but only if the request
+ // does not ignore location settings
receiver.callProviderEnabledLocked(name, false);
}
+
+ applyRequirementsLocked(name);
+
// Update the monitoring here just in case multiple location requests were added to the
// same receiver (this request may be high power and the initial might not have been).
receiver.updateMonitoring(true);
@@ -2865,33 +2959,11 @@
long identity = Binder.clearCallingIdentity();
try {
- boolean enabled;
- try {
- enabled = Settings.Secure.getIntForUser(
+ return Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.LOCATION_MODE,
+ Settings.Secure.LOCATION_MODE_OFF,
userId) != Settings.Secure.LOCATION_MODE_OFF;
- } catch (Settings.SettingNotFoundException e) {
- // OS upgrade case where mode isn't set yet
- enabled = !TextUtils.isEmpty(Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- userId));
-
- try {
- Settings.Secure.putIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCATION_MODE,
- enabled
- ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY
- : Settings.Secure.LOCATION_MODE_OFF,
- userId);
- } catch (RuntimeException ex) {
- // any problem with writing should not be propagated
- Slog.e(TAG, "error updating location mode", ex);
- }
- }
- return enabled;
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -2981,26 +3053,29 @@
return;
}
- if (!provider.isPassiveLocked()) {
- // notify passive provider of the new location
- mPassiveProvider.updateLocation(location);
+ // only notify passive provider and update last location for locations that come from
+ // useable providers
+ if (provider.isUseableLocked()) {
+ if (!provider.isPassiveLocked()) {
+ mPassiveProvider.updateLocation(location);
+ }
}
if (D) Log.d(TAG, "incoming location: " + location);
long now = SystemClock.elapsedRealtime();
- updateLastLocationLocked(location, provider.getName());
- // mLastLocation should have been updated from the updateLastLocationLocked call above.
- Location lastLocation = mLastLocation.get(provider.getName());
- if (lastLocation == null) {
- Log.e(TAG, "handleLocationChangedLocked() updateLastLocation failed");
- return;
+ if (provider.isUseableLocked()) {
+ updateLastLocationLocked(location, provider.getName());
}
// Update last known coarse interval location if enough time has passed.
- Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider.getName());
+ Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(
+ provider.getName());
if (lastLocationCoarseInterval == null) {
lastLocationCoarseInterval = new Location(location);
- mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
+
+ if (provider.isUseableLocked()) {
+ mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
+ }
}
long timeDiffNanos = location.getElapsedRealtimeNanos()
- lastLocationCoarseInterval.getElapsedRealtimeNanos();
@@ -3031,6 +3106,10 @@
Receiver receiver = r.mReceiver;
boolean receiverDead = false;
+ if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) {
+ continue;
+ }
+
int receiverUserId = UserHandle.getUserId(receiver.mIdentity.mUid);
if (!isCurrentProfileLocked(receiverUserId)
&& !isLocationProviderLocked(receiver.mIdentity.mUid)) {
@@ -3066,7 +3145,7 @@
if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
notifyLocation = coarseLocation; // use coarse location
} else {
- notifyLocation = lastLocation; // use fine location
+ notifyLocation = location; // use fine location
}
if (notifyLocation != null) {
Location lastLoc = r.mLastFixBroadcast;
diff --git a/services/core/java/com/android/server/LooperStatsService.java b/services/core/java/com/android/server/LooperStatsService.java
index cee98c1..4a8706e 100644
--- a/services/core/java/com/android/server/LooperStatsService.java
+++ b/services/core/java/com/android/server/LooperStatsService.java
@@ -122,6 +122,10 @@
"exception_count"));
pw.println(header);
for (LooperStats.ExportedEntry entry : entries) {
+ if (entry.messageName.startsWith(LooperStats.DEBUG_ENTRY_PREFIX)) {
+ // Do not dump debug entries.
+ continue;
+ }
pw.printf("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
packageMap.mapUid(entry.workSourceUid),
entry.threadName,
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index c84389b..89ff338 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -46,7 +46,9 @@
import android.app.ActivityManager;
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.InetAddresses;
import android.net.INetd;
+import android.net.INetdUnsolicitedEventListener;
import android.net.INetworkManagementEventObserver;
import android.net.ITetheringStatsProvider;
import android.net.InterfaceConfiguration;
@@ -60,7 +62,7 @@
import android.net.RouteInfo;
import android.net.TetherStatsParcel;
import android.net.UidRange;
-import android.net.util.NetdService;
+import android.net.shared.NetdService;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Handler;
@@ -205,6 +207,8 @@
private INetd mNetdService;
+ private final NetdUnsolicitedEventListener mNetdUnsolicitedEventListener;
+
private IBatteryStats mBatteryStats;
private final Thread mThread;
@@ -322,6 +326,8 @@
mDaemonHandler = new Handler(FgThread.get().getLooper());
+ mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener();
+
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
@@ -340,6 +346,7 @@
mFgHandler = null;
mThread = null;
mServices = null;
+ mNetdUnsolicitedEventListener = null;
}
static NetworkManagementService create(Context context, String socket, SystemServices services)
@@ -446,7 +453,6 @@
// our sanity-checking state.
mActiveAlerts.remove(iface);
mActiveQuotas.remove(iface);
-
invokeForAllObservers(o -> o.interfaceRemoved(iface));
}
@@ -552,7 +558,7 @@
return;
}
// No current code examines the interface parameter in a global alert. Just pass null.
- notifyLimitReached(LIMIT_GLOBAL_ALERT, null);
+ mDaemonHandler.post(() -> notifyLimitReached(LIMIT_GLOBAL_ALERT, null));
}
}
@@ -583,6 +589,12 @@
private void connectNativeNetdService() {
mNetdService = mServices.getNetd();
+ try {
+ mNetdService.registerUnsolicitedEventListener(mNetdUnsolicitedEventListener);
+ if (DBG) Slog.d(TAG, "Register unsolicited event listener");
+ } catch (RemoteException | ServiceSpecificException e) {
+ Slog.e(TAG, "Failed to set Netd unsolicited event listener " + e);
+ }
}
/**
@@ -709,14 +721,96 @@
/**
* Notify our observers of a route change.
*/
- private void notifyRouteChange(String action, RouteInfo route) {
- if (action.equals("updated")) {
+ private void notifyRouteChange(boolean updated, RouteInfo route) {
+ if (updated) {
invokeForAllObservers(o -> o.routeUpdated(route));
} else {
invokeForAllObservers(o -> o.routeRemoved(route));
}
}
+ private class NetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub {
+ @Override
+ public void onInterfaceClassActivityChanged(boolean isActive,
+ int label, long timestamp, int uid) throws RemoteException {
+ final long timestampNanos;
+ if (timestamp <= 0) {
+ timestampNanos = SystemClock.elapsedRealtimeNanos();
+ } else {
+ timestampNanos = timestamp;
+ }
+ mDaemonHandler.post(() -> notifyInterfaceClassActivity(label,
+ isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
+ : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ timestampNanos, uid, false));
+ }
+
+ @Override
+ public void onQuotaLimitReached(String alertName, String ifName)
+ throws RemoteException {
+ mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName));
+ }
+
+ @Override
+ public void onInterfaceDnsServerInfo(String ifName,
+ long lifetime, String[] servers) throws RemoteException {
+ mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers));
+ }
+
+ @Override
+ public void onInterfaceAddressUpdated(String addr,
+ String ifName, int flags, int scope) throws RemoteException {
+ final LinkAddress address = new LinkAddress(addr, flags, scope);
+ mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address));
+ }
+
+ @Override
+ public void onInterfaceAddressRemoved(String addr,
+ String ifName, int flags, int scope) throws RemoteException {
+ final LinkAddress address = new LinkAddress(addr, flags, scope);
+ mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address));
+ }
+
+ @Override
+ public void onInterfaceAdded(String ifName) throws RemoteException {
+ mDaemonHandler.post(() -> notifyInterfaceAdded(ifName));
+ }
+
+ @Override
+ public void onInterfaceRemoved(String ifName) throws RemoteException {
+ mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName));
+ }
+
+ @Override
+ public void onInterfaceChanged(String ifName, boolean up)
+ throws RemoteException {
+ mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up));
+ }
+
+ @Override
+ public void onInterfaceLinkStateChanged(String ifName, boolean up)
+ throws RemoteException {
+ mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up));
+ }
+
+ @Override
+ public void onRouteChanged(boolean updated,
+ String route, String gateway, String ifName) throws RemoteException {
+ final RouteInfo processRoute = new RouteInfo(new IpPrefix(route),
+ ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway),
+ ifName);
+ mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute));
+ }
+
+ @Override
+ public void onStrictCleartextDetected(int uid, String hex) throws RemoteException {
+ // Don't need to post to mDaemonHandler because the only thing
+ // that notifyCleartextNetwork does is post to a handler
+ ActivityManager.getService().notifyCleartextNetwork(uid,
+ HexDump.hexStringToByteArray(hex));
+ }
+ }
+
//
// Netd Callback handling
//
@@ -904,7 +998,7 @@
InetAddress gateway = null;
if (via != null) gateway = InetAddress.parseNumericAddress(via);
RouteInfo route = new RouteInfo(new IpPrefix(cooked[3]), gateway, dev);
- notifyRouteChange(cooked[2], route);
+ notifyRouteChange(cooked[2].equals("updated"), route);
return true;
} catch (IllegalArgumentException e) {}
}
@@ -1367,13 +1461,9 @@
if (ConnectivityManager.isNetworkTypeMobile(type)) {
mNetworkActive = false;
}
- mDaemonHandler.post(new Runnable() {
- @Override public void run() {
- notifyInterfaceClassActivity(type,
- DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
- SystemClock.elapsedRealtimeNanos(), -1, false);
- }
- });
+ mDaemonHandler.post(() -> notifyInterfaceClassActivity(type,
+ DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ SystemClock.elapsedRealtimeNanos(), -1, false));
}
}
@@ -1396,13 +1486,9 @@
throw new IllegalStateException(e);
}
mActiveIdleTimers.remove(iface);
- mDaemonHandler.post(new Runnable() {
- @Override public void run() {
- notifyInterfaceClassActivity(params.type,
- DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
- SystemClock.elapsedRealtimeNanos(), -1, false);
- }
- });
+ mDaemonHandler.post(() -> notifyInterfaceClassActivity(params.type,
+ DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ SystemClock.elapsedRealtimeNanos(), -1, false));
}
}
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
index 1e9a007..190fff1 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
@@ -31,6 +31,19 @@
*/
byte[] getFrpCredentialHandle();
+ /** Stores the data used to enable the Test Harness Mode after factory-resetting. */
+ void setTestHarnessModeData(byte[] data);
+
+ /**
+ * Retrieves the data used to place the device into Test Harness Mode.
+ *
+ * @throws IllegalStateException if the underlying storage is corrupt or inaccessible.
+ */
+ byte[] getTestHarnessModeData();
+
+ /** Clear out the Test Harness Mode data. */
+ void clearTestHarnessModeData();
+
/** Update the OEM unlock enabled bit, bypassing user restriction checks. */
void forceOemUnlockEnabled(boolean enabled);
}
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 21093b9..bd5ad96 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static com.android.internal.util.Preconditions.checkArgument;
+
import android.Manifest;
import android.app.ActivityManager;
import android.content.Context;
@@ -28,12 +30,10 @@
import android.os.UserManager;
import android.service.persistentdata.IPersistentDataBlockService;
import android.service.persistentdata.PersistentDataBlockManager;
-import android.util.Log;
import android.util.Slog;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
import libcore.io.IoUtils;
@@ -65,6 +65,40 @@
*
* Clients can read any number of bytes from the currently written block up to its total size by
* invoking {@link IPersistentDataBlockService#read}
+ *
+ * The persistent data block is currently laid out as follows:
+ * | ---------BEGINNING OF PARTITION-------------|
+ * | Partition digest (32 bytes) |
+ * | --------------------------------------------|
+ * | PARTITION_TYPE_MARKER (4 bytes) |
+ * | --------------------------------------------|
+ * | FRP data block length (4 bytes) |
+ * | --------------------------------------------|
+ * | FRP data (variable length) |
+ * | --------------------------------------------|
+ * | ... |
+ * | --------------------------------------------|
+ * | Test mode data block (10000 bytes) |
+ * | --------------------------------------------|
+ * | | Test mode data length (4 bytes) |
+ * | --------------------------------------------|
+ * | | Test mode data (variable length) |
+ * | | ... |
+ * | --------------------------------------------|
+ * | FRP credential handle block (1000 bytes) |
+ * | --------------------------------------------|
+ * | | FRP credential handle length (4 bytes)|
+ * | --------------------------------------------|
+ * | | FRP credential handle (variable len) |
+ * | | ... |
+ * | --------------------------------------------|
+ * | OEM Unlock bit (1 byte) |
+ * | ---------END OF PARTITION-------------------|
+ *
+ * TODO: now that the persistent partition contains several blocks, next time someone wants a new
+ * block, we should look at adding more generic block definitions and get rid of the various raw
+ * XXX_RESERVED_SIZE and XXX_DATA_SIZE constants. That will ensure the code is easier to maintain
+ * and less likely to introduce out-of-bounds read/write.
*/
public class PersistentDataBlockService extends SystemService {
private static final String TAG = PersistentDataBlockService.class.getSimpleName();
@@ -73,10 +107,16 @@
private static final int HEADER_SIZE = 8;
// Magic number to mark block device as adhering to the format consumed by this service
private static final int PARTITION_TYPE_MARKER = 0x19901873;
- /** Size of the block reserved for FPR credential, including 4 bytes for the size header. */
+ /** Size of the block reserved for FRP credential, including 4 bytes for the size header. */
private static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000;
/** Maximum size of the FRP credential handle that can be stored. */
private static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4;
+ /**
+ * Size of the block reserved for Test Harness Mode data, including 4 bytes for the size header.
+ */
+ private static final int TEST_MODE_RESERVED_SIZE = 10000;
+ /** Maximum size of the Test Harness Mode data that can be stored. */
+ private static final int MAX_TEST_MODE_DATA_SIZE = TEST_MODE_RESERVED_SIZE - 4;
// Limit to 100k as blocks larger than this might cause strain on Binder.
private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
@@ -221,6 +261,14 @@
return mBlockDeviceSize;
}
+ private long getFrpCredentialDataOffset() {
+ return getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE;
+ }
+
+ private long getTestHarnessModeDataOffset() {
+ return getFrpCredentialDataOffset() - TEST_MODE_RESERVED_SIZE;
+ }
+
private boolean enforceChecksumValidity() {
byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
@@ -383,7 +431,7 @@
private long doGetMaximumDataBlockSize() {
long actualSize = getBlockDeviceSize() - HEADER_SIZE - DIGEST_SIZE_BYTES
- - FRP_CREDENTIAL_RESERVED_SIZE - 1;
+ - TEST_MODE_RESERVED_SIZE - FRP_CREDENTIAL_RESERVED_SIZE - 1;
return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
}
@@ -391,6 +439,13 @@
private native int nativeWipe(String path);
private final IBinder mService = new IPersistentDataBlockService.Stub() {
+
+ /**
+ * Write the data to the persistent data block.
+ *
+ * @return a positive integer of the number of bytes that were written if successful,
+ * otherwise a negative integer indicating there was a problem
+ */
@Override
public int write(byte[] data) throws RemoteException {
enforceUid(Binder.getCallingUid());
@@ -597,12 +652,51 @@
@Override
public void setFrpCredentialHandle(byte[] handle) {
- Preconditions.checkArgument(handle == null || handle.length > 0,
- "handle must be null or non-empty");
- Preconditions.checkArgument(handle == null
- || handle.length <= MAX_FRP_CREDENTIAL_HANDLE_SIZE,
- "handle must not be longer than " + MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+ writeInternal(handle, getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+ }
+ @Override
+ public byte[] getFrpCredentialHandle() {
+ return readInternal(getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+ }
+
+ @Override
+ public void setTestHarnessModeData(byte[] data) {
+ writeInternal(data, getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE);
+ }
+
+ @Override
+ public byte[] getTestHarnessModeData() {
+ byte[] data = readInternal(getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE);
+ if (data == null) {
+ return new byte[0];
+ }
+ return data;
+ }
+
+ @Override
+ public void clearTestHarnessModeData() {
+ int size = Math.min(MAX_TEST_MODE_DATA_SIZE, getTestHarnessModeData().length) + 4;
+ writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size));
+ }
+
+ private void writeInternal(byte[] data, long offset, int dataLength) {
+ checkArgument(data == null || data.length > 0, "data must be null or non-empty");
+ checkArgument(
+ data == null || data.length <= dataLength,
+ "data must not be longer than " + dataLength);
+
+ ByteBuffer dataBuffer = ByteBuffer.allocate(dataLength + 4);
+ dataBuffer.putInt(data == null ? 0 : data.length);
+ if (data != null) {
+ dataBuffer.put(data);
+ }
+ dataBuffer.flip();
+
+ writeDataBuffer(offset, dataBuffer);
+ }
+
+ private void writeDataBuffer(long offset, ByteBuffer dataBuffer) {
FileOutputStream outputStream;
try {
outputStream = new FileOutputStream(new File(mDataBlockFile));
@@ -610,25 +704,15 @@
Slog.e(TAG, "partition not available", e);
return;
}
-
- ByteBuffer data = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE);
- data.putInt(handle == null ? 0 : handle.length);
- if (handle != null) {
- data.put(handle);
- }
- data.flip();
-
synchronized (mLock) {
if (!mIsWritable) {
IoUtils.closeQuietly(outputStream);
return;
}
-
try {
FileChannel channel = outputStream.getChannel();
-
- channel.position(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE);
- channel.write(data);
+ channel.position(offset);
+ channel.write(dataBuffer);
outputStream.flush();
} catch (IOException e) {
Slog.e(TAG, "unable to access persistent partition", e);
@@ -641,8 +725,7 @@
}
}
- @Override
- public byte[] getFrpCredentialHandle() {
+ private byte[] readInternal(long offset, int maxLength) {
if (!enforceChecksumValidity()) {
throw new IllegalStateException("invalid checksum");
}
@@ -652,14 +735,14 @@
inputStream = new DataInputStream(
new FileInputStream(new File(mDataBlockFile)));
} catch (FileNotFoundException e) {
- throw new IllegalStateException("frp partition not available");
+ throw new IllegalStateException("persistent partition not available");
}
try {
synchronized (mLock) {
- inputStream.skip(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE);
+ inputStream.skip(offset);
int length = inputStream.readInt();
- if (length <= 0 || length > MAX_FRP_CREDENTIAL_HANDLE_SIZE) {
+ if (length <= 0 || length > maxLength) {
return null;
}
byte[] bytes = new byte[length];
@@ -667,7 +750,7 @@
return bytes;
}
} catch (IOException e) {
- throw new IllegalStateException("frp handle not readable", e);
+ throw new IllegalStateException("persistent partition not readable", e);
} finally {
IoUtils.closeQuietly(inputStream);
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 7731c04..9d810cd 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1706,8 +1706,8 @@
int uid, String packageName, int[] ops) {
long maxTime = 0;
final List<AppOpsManager.PackageOps> pkgs = manager.getOpsForPackage(uid, packageName, ops);
- for (AppOpsManager.PackageOps pkg : CollectionUtils.defeatNullable(pkgs)) {
- for (AppOpsManager.OpEntry op : CollectionUtils.defeatNullable(pkg.getOps())) {
+ for (AppOpsManager.PackageOps pkg : CollectionUtils.emptyIfNull(pkgs)) {
+ for (AppOpsManager.OpEntry op : CollectionUtils.emptyIfNull(pkg.getOps())) {
maxTime = Math.max(maxTime, op.getLastAccessTime());
}
}
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 16b12f1..1870f8d 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -2,6 +2,7 @@
"presubmit": [
{
"name": "FrameworksMockingServicesTests",
+ "file_patterns": ["AlarmManagerService\\.java"],
"options": [
{
"include-annotation": "android.platform.test.annotations.Presubmit"
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index aa23890..e543617 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2115,6 +2115,16 @@
android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
}
+ if ((events & PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED) != 0) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
+ }
+
+ if ((events & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) != 0) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
+ }
+
return true;
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index e879efd..c826df0 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5202,7 +5202,7 @@
Process.SYSTEM_UID, null /* packageName */, false);
fout.println("Accounts: " + accounts.length);
for (Account account : accounts) {
- fout.println(" " + account.toSafeString());
+ fout.println(" " + account.toString());
}
// Add debug information.
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index ccead6c..c7b9a3c 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -18,6 +18,7 @@
import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull;
+import android.annotation.TestApi;
import android.app.ActivityManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
@@ -26,6 +27,7 @@
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
+import android.debug.AdbProtoEnums;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.Environment;
@@ -37,21 +39,36 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.service.adb.AdbDebuggingManagerProto;
+import android.util.AtomicFile;
import android.util.Base64;
import android.util.Slog;
+import android.util.StatsLog;
+import android.util.Xml;
import com.android.internal.R;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.FgThread;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
/**
* Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keysi
@@ -63,6 +80,7 @@
private static final String ADBD_SOCKET = "adbd";
private static final String ADB_DIRECTORY = "misc/adb";
+ // This file contains keys that will always be allowed to connect to the device via adb.
private static final String ADB_KEYS_FILE = "adb_keys";
private static final int BUFFER_SIZE = 4096;
@@ -71,12 +89,25 @@
private AdbDebuggingThread mThread;
private boolean mAdbEnabled = false;
private String mFingerprints;
+ private String mConnectedKey;
+ private String mConfirmComponent;
public AdbDebuggingManager(Context context) {
mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
mContext = context;
}
+ /**
+ * Constructor that accepts the component to be invoked to confirm if the user wants to allow
+ * an adb connection from the key.
+ */
+ @TestApi
+ protected AdbDebuggingManager(Context context, String confirmComponent) {
+ mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
+ mContext = context;
+ mConfirmComponent = confirmComponent;
+ }
+
class AdbDebuggingThread extends Thread {
private boolean mStopped;
private LocalSocket mSocket;
@@ -135,7 +166,9 @@
byte[] buffer = new byte[BUFFER_SIZE];
while (true) {
int count = mInputStream.read(buffer);
- if (count < 0) {
+ // if less than 2 bytes are read the if statements below will throw an
+ // IndexOutOfBoundsException.
+ if (count < 2) {
break;
}
@@ -146,6 +179,11 @@
AdbDebuggingHandler.MESSAGE_ADB_CONFIRM);
msg.obj = key;
mHandler.sendMessage(msg);
+ } else if (buffer[0] == 'D' && buffer[1] == 'C') {
+ Slog.d(TAG, "Received disconnected message");
+ Message msg = mHandler.obtainMessage(
+ AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT);
+ mHandler.sendMessage(msg);
} else {
Slog.e(TAG, "Wrong message: "
+ (new String(Arrays.copyOfRange(buffer, 0, 2))));
@@ -202,15 +240,41 @@
}
class AdbDebuggingHandler extends Handler {
- private static final int MESSAGE_ADB_ENABLED = 1;
- private static final int MESSAGE_ADB_DISABLED = 2;
- private static final int MESSAGE_ADB_ALLOW = 3;
- private static final int MESSAGE_ADB_DENY = 4;
- private static final int MESSAGE_ADB_CONFIRM = 5;
- private static final int MESSAGE_ADB_CLEAR = 6;
+ // The time to schedule the job to keep the key store updated with a currently connected
+ // key. This job is required because a deveoper could keep a device connected to their
+ // system beyond the time within which a subsequent connection is allowed. But since the
+ // last connection time is only written when a device is connected and disconnected then
+ // if the device is rebooted while connected to the development system it would appear as
+ // though the adb grant for the system is no longer authorized and the developer would need
+ // to manually allow the connection again.
+ private static final long UPDATE_KEY_STORE_JOB_INTERVAL = 86400000;
+
+ static final int MESSAGE_ADB_ENABLED = 1;
+ static final int MESSAGE_ADB_DISABLED = 2;
+ static final int MESSAGE_ADB_ALLOW = 3;
+ static final int MESSAGE_ADB_DENY = 4;
+ static final int MESSAGE_ADB_CONFIRM = 5;
+ static final int MESSAGE_ADB_CLEAR = 6;
+ static final int MESSAGE_ADB_DISCONNECT = 7;
+ static final int MESSAGE_ADB_PERSIST_KEY_STORE = 8;
+ static final int MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME = 9;
+
+ private AdbKeyStore mAdbKeyStore;
AdbDebuggingHandler(Looper looper) {
super(looper);
+ mAdbKeyStore = new AdbKeyStore();
+ }
+
+ /**
+ * Constructor that accepts the AdbDebuggingThread to which responses should be sent
+ * and the AdbKeyStore to be used to store the temporary grants.
+ */
+ @TestApi
+ AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread, AdbKeyStore adbKeyStore) {
+ super(looper);
+ mThread = thread;
+ mAdbKeyStore = adbKeyStore;
}
public void handleMessage(Message msg) {
@@ -251,12 +315,15 @@
break;
}
- if (msg.arg1 == 1) {
- writeKey(key);
- }
-
+ boolean alwaysAllow = msg.arg1 == 1;
if (mThread != null) {
mThread.sendResponse("OK");
+ if (alwaysAllow) {
+ mConnectedKey = key;
+ mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+ scheduleJobToUpdateAdbKeyStore();
+ }
+ logAdbConnectionChanged(key, AdbProtoEnums.USER_ALLOWED, alwaysAllow);
}
break;
}
@@ -264,36 +331,88 @@
case MESSAGE_ADB_DENY:
if (mThread != null) {
mThread.sendResponse("NO");
+ logAdbConnectionChanged(null, AdbProtoEnums.USER_DENIED, false);
}
break;
case MESSAGE_ADB_CONFIRM: {
+ String key = (String) msg.obj;
if ("trigger_restart_min_framework".equals(
SystemProperties.get("vold.decrypt"))) {
Slog.d(TAG, "Deferring adb confirmation until after vold decrypt");
if (mThread != null) {
mThread.sendResponse("NO");
+ logAdbConnectionChanged(key, AdbProtoEnums.DENIED_VOLD_DECRYPT, false);
}
break;
}
- String key = (String) msg.obj;
String fingerprints = getFingerprints(key);
if ("".equals(fingerprints)) {
if (mThread != null) {
mThread.sendResponse("NO");
+ logAdbConnectionChanged(key, AdbProtoEnums.DENIED_INVALID_KEY, false);
}
break;
}
- mFingerprints = fingerprints;
- startConfirmation(key, mFingerprints);
+ // Check if the key should be allowed without user interaction.
+ if (mAdbKeyStore.isKeyAuthorized(key)) {
+ if (mThread != null) {
+ mThread.sendResponse("OK");
+ mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+ logAdbConnectionChanged(key, AdbProtoEnums.AUTOMATICALLY_ALLOWED, true);
+ mConnectedKey = key;
+ scheduleJobToUpdateAdbKeyStore();
+ }
+ } else {
+ logAdbConnectionChanged(key, AdbProtoEnums.AWAITING_USER_APPROVAL, false);
+ mFingerprints = fingerprints;
+ startConfirmation(key, mFingerprints);
+ }
break;
}
- case MESSAGE_ADB_CLEAR:
+ case MESSAGE_ADB_CLEAR: {
deleteKeyFile();
+ mConnectedKey = null;
+ mAdbKeyStore.deleteKeyStore();
+ cancelJobToUpdateAdbKeyStore();
break;
+ }
+
+ case MESSAGE_ADB_DISCONNECT: {
+ if (mConnectedKey != null) {
+ mAdbKeyStore.setLastConnectionTime(mConnectedKey,
+ System.currentTimeMillis());
+ cancelJobToUpdateAdbKeyStore();
+ }
+ logAdbConnectionChanged(mConnectedKey, AdbProtoEnums.DISCONNECTED,
+ (mConnectedKey != null));
+ mConnectedKey = null;
+ break;
+ }
+
+ case MESSAGE_ADB_PERSIST_KEY_STORE: {
+ mAdbKeyStore.persistKeyStore();
+ break;
+ }
+
+ case MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME: {
+ if (mConnectedKey != null) {
+ mAdbKeyStore.setLastConnectionTime(mConnectedKey,
+ System.currentTimeMillis());
+ scheduleJobToUpdateAdbKeyStore();
+ }
+ break;
+ }
}
}
+
+ private void logAdbConnectionChanged(String key, int state, boolean alwaysAllow) {
+ long lastConnectionTime = mAdbKeyStore.getLastConnectionTime(key);
+ long authWindow = mAdbKeyStore.getAllowedConnectionTime();
+ StatsLog.write(StatsLog.ADB_CONNECTION_CHANGED, lastConnectionTime, authWindow, state,
+ alwaysAllow);
+ }
}
private String getFingerprints(String key) {
@@ -335,7 +454,8 @@
UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
String componentString;
if (userInfo.isAdmin()) {
- componentString = Resources.getSystem().getString(
+ componentString = mConfirmComponent != null
+ ? mConfirmComponent : Resources.getSystem().getString(
com.android.internal.R.string.config_customAdbPublicKeyConfirmationComponent);
} else {
// If the current foreground user is not the admin user we send a different
@@ -397,7 +517,10 @@
return intent;
}
- private File getUserKeyFile() {
+ /**
+ * Returns a new File with the specified name in the adb directory.
+ */
+ private File getAdbFile(String fileName) {
File dataDir = Environment.getDataDirectory();
File adbDir = new File(dataDir, ADB_DIRECTORY);
@@ -406,7 +529,11 @@
return null;
}
- return new File(adbDir, ADB_KEYS_FILE);
+ return new File(adbDir, fileName);
+ }
+
+ private File getUserKeyFile() {
+ return getAdbFile(ADB_KEYS_FILE);
}
private void writeKey(String key) {
@@ -476,6 +603,36 @@
}
/**
+ * Sends a message to the handler to persist the key store.
+ */
+ private void sendPersistKeyStoreMessage() {
+ Message msg = mHandler.obtainMessage(AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEY_STORE);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Schedules a job to update the connection time of the currently connected key. This is
+ * intended for cases such as development devices that are left connected to a user's
+ * system beyond the window within which a connection is allowed without user interaction.
+ * A job should be rescheduled daily so that if the device is rebooted while connected to
+ * the user's system the last time in the key store will show within 24 hours which should
+ * be within the allowed window.
+ */
+ private void scheduleJobToUpdateAdbKeyStore() {
+ Message message = mHandler.obtainMessage(
+ AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME);
+ mHandler.sendMessageDelayed(message, AdbDebuggingHandler.UPDATE_KEY_STORE_JOB_INTERVAL);
+ }
+
+ /**
+ * Cancels the scheduled job to update the connection time of the currently connected key.
+ * This should be invoked once the adb session is disconnected.
+ */
+ private void cancelJobToUpdateAdbKeyStore() {
+ mHandler.removeMessages(AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME);
+ }
+
+ /**
* Dump the USB debugging state.
*/
public void dump(DualDumpOutputStream dump, String idName, long id) {
@@ -501,4 +658,231 @@
dump.end(token);
}
+
+ /**
+ * Handles adb keys for which the user has granted the 'always allow' option. This class ensures
+ * these grants are revoked after a period of inactivity as specified in the
+ * ADB_ALLOWED_CONNECTION_TIME setting.
+ */
+ class AdbKeyStore {
+ private Map<String, Long> mKeyMap;
+ private File mKeyFile;
+ private AtomicFile mAtomicKeyFile;
+ // This file contains keys that will be allowed to connect without user interaction as long
+ // as a subsequent connection occurs within the allowed duration.
+ private static final String ADB_TEMP_KEYS_FILE = "adb_temp_keys.xml";
+ private static final String XML_TAG_ADB_KEY = "adbKey";
+ private static final String XML_ATTRIBUTE_KEY = "key";
+ private static final String XML_ATTRIBUTE_LAST_CONNECTION = "lastConnection";
+
+ /**
+ * Value returned by {@code getLastConnectionTime} when there is no previously saved
+ * connection time for the specified key.
+ */
+ public static final long NO_PREVIOUS_CONNECTION = 0;
+
+ /**
+ * Constructor that uses the default location for the persistent adb key store.
+ */
+ AdbKeyStore() {
+ initKeyFile();
+ mKeyMap = getKeyMapFromFile();
+ }
+
+ /**
+ * Constructor that uses the specified file as the location for the persistent adb key
+ * store.
+ */
+ AdbKeyStore(File keyFile) {
+ mKeyFile = keyFile;
+ initKeyFile();
+ mKeyMap = getKeyMapFromFile();
+ }
+
+ /**
+ * Initializes the key file that will be used to persist the adb grants.
+ */
+ private void initKeyFile() {
+ if (mKeyFile == null) {
+ mKeyFile = getAdbFile(ADB_TEMP_KEYS_FILE);
+ }
+ // getAdbFile can return null if the adb file cannot be obtained
+ if (mKeyFile != null) {
+ mAtomicKeyFile = new AtomicFile(mKeyFile);
+ }
+ }
+
+ /**
+ * Returns the key map with the keys and last connection times from the key file.
+ */
+ private Map<String, Long> getKeyMapFromFile() {
+ Map<String, Long> keyMap = new HashMap<String, Long>();
+ // if the AtomicFile could not be instantiated before attempt again; if it still fails
+ // return an empty key map.
+ if (mAtomicKeyFile == null) {
+ initKeyFile();
+ if (mAtomicKeyFile == null) {
+ Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
+ return keyMap;
+ }
+ }
+ if (!mAtomicKeyFile.exists()) {
+ return keyMap;
+ }
+ try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(keyStream, StandardCharsets.UTF_8.name());
+ XmlUtils.beginDocument(parser, XML_TAG_ADB_KEY);
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (tagName == null) {
+ break;
+ } else if (!tagName.equals(XML_TAG_ADB_KEY)) {
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
+ long connectionTime;
+ try {
+ connectionTime = Long.valueOf(
+ parser.getAttributeValue(null, XML_ATTRIBUTE_LAST_CONNECTION));
+ } catch (NumberFormatException e) {
+ Slog.e(TAG,
+ "Caught a NumberFormatException parsing the last connection time: "
+ + e);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ keyMap.put(key, connectionTime);
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(TAG, "Caught an exception parsing the XML key file: ", e);
+ }
+ return keyMap;
+ }
+
+ /**
+ * Writes the key map to the key file.
+ */
+ public void persistKeyStore() {
+ // if there is nothing in the key map then ensure any keys left in the key store files
+ // are deleted as well.
+ if (mKeyMap.size() == 0) {
+ deleteKeyStore();
+ return;
+ }
+ if (mAtomicKeyFile == null) {
+ initKeyFile();
+ if (mAtomicKeyFile == null) {
+ Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for writing");
+ return;
+ }
+ }
+ FileOutputStream keyStream = null;
+ try {
+ XmlSerializer serializer = new FastXmlSerializer();
+ keyStream = mAtomicKeyFile.startWrite();
+ serializer.setOutput(keyStream, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ long allowedTime = getAllowedConnectionTime();
+ long systemTime = System.currentTimeMillis();
+ Iterator keyMapIterator = mKeyMap.entrySet().iterator();
+ while (keyMapIterator.hasNext()) {
+ Map.Entry<String, Long> keyEntry = (Map.Entry) keyMapIterator.next();
+ long connectionTime = keyEntry.getValue();
+ if (systemTime < (connectionTime + allowedTime)) {
+ serializer.startTag(null, XML_TAG_ADB_KEY);
+ serializer.attribute(null, XML_ATTRIBUTE_KEY, keyEntry.getKey());
+ serializer.attribute(null, XML_ATTRIBUTE_LAST_CONNECTION,
+ String.valueOf(keyEntry.getValue()));
+ serializer.endTag(null, XML_TAG_ADB_KEY);
+ } else {
+ keyMapIterator.remove();
+ }
+ }
+ serializer.endDocument();
+ mAtomicKeyFile.finishWrite(keyStream);
+ } catch (IOException e) {
+ Slog.e(TAG, "Caught an exception writing the key map: ", e);
+ mAtomicKeyFile.failWrite(keyStream);
+ }
+ }
+
+ /**
+ * Removes all of the entries in the key map and deletes the key file.
+ */
+ public void deleteKeyStore() {
+ mKeyMap.clear();
+ if (mAtomicKeyFile == null) {
+ return;
+ }
+ mAtomicKeyFile.delete();
+ }
+
+ /**
+ * Returns the time of the last connection from the specified key, or {@code
+ * NO_PREVIOUS_CONNECTION} if the specified key does not have an active adb grant.
+ */
+ public long getLastConnectionTime(String key) {
+ return mKeyMap.getOrDefault(key, NO_PREVIOUS_CONNECTION);
+ }
+
+ /**
+ * Sets the time of the last connection for the specified key to the provided time.
+ */
+ public void setLastConnectionTime(String key, long connectionTime) {
+ // Do not set the connection time to a value that is earlier than what was previously
+ // stored as the last connection time.
+ if (mKeyMap.containsKey(key) && mKeyMap.get(key) >= connectionTime) {
+ return;
+ }
+ mKeyMap.put(key, connectionTime);
+ sendPersistKeyStoreMessage();
+ }
+
+ /**
+ * Returns whether the specified key should be authroized to connect without user
+ * interaction. This requires that the user previously connected this device and selected
+ * the option to 'Always allow', and the time since the last connection is within the
+ * allowed window.
+ */
+ public boolean isKeyAuthorized(String key) {
+ long lastConnectionTime = getLastConnectionTime(key);
+ if (lastConnectionTime == NO_PREVIOUS_CONNECTION) {
+ return false;
+ }
+ long allowedConnectionTime = getAllowedConnectionTime();
+ // if the allowed connection time is 0 then revert to the previous behavior of always
+ // allowing previously granted adb grants.
+ if (allowedConnectionTime == 0 || (System.currentTimeMillis() < (lastConnectionTime
+ + allowedConnectionTime))) {
+ return true;
+ } else {
+ // since this key is no longer auhorized remove it from the Map
+ removeKey(key);
+ return false;
+ }
+ }
+
+ /**
+ * Returns the connection time within which a connection from an allowed key is
+ * automatically allowed without user interaction.
+ */
+ public long getAllowedConnectionTime() {
+ return Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
+ Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
+ }
+
+ /**
+ * Removes the specified key from the key store.
+ */
+ public void removeKey(String key) {
+ if (!mKeyMap.containsKey(key)) {
+ return;
+ }
+ mKeyMap.remove(key);
+ sendPersistKeyStoreMessage();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 089847d..0d1702d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -64,6 +64,7 @@
import static android.os.Process.SIGNAL_USR1;
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
+import static android.os.Process.ZYGOTE_PROCESS;
import static android.os.Process.getTotalMemory;
import static android.os.Process.isThreadInProcess;
import static android.os.Process.killProcess;
@@ -75,7 +76,6 @@
import static android.os.Process.sendSignal;
import static android.os.Process.setThreadPriority;
import static android.os.Process.setThreadScheduler;
-import static android.os.Process.zygoteProcess;
import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
import static android.provider.Settings.Global.DEBUG_APP;
import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS;
@@ -1607,19 +1607,8 @@
}
} break;
case UPDATE_HTTP_PROXY_MSG: {
- ProxyInfo proxy = (ProxyInfo)msg.obj;
- String host = "";
- String port = "";
- String exclList = "";
- Uri pacFileUrl = Uri.EMPTY;
- if (proxy != null) {
- host = proxy.getHost();
- port = Integer.toString(proxy.getPort());
- exclList = proxy.getExclusionListAsString();
- pacFileUrl = proxy.getPacFileUrl();
- }
synchronized (ActivityManagerService.this) {
- mProcessList.setAllHttpProxyLocked(host, port, exclList, pacFileUrl);
+ mProcessList.setAllHttpProxyLocked();
}
} break;
case PROC_START_TIMEOUT_MSG: {
@@ -2162,7 +2151,7 @@
? Collections.emptyList()
: Arrays.asList(exemptions.split(","));
}
- if (!zygoteProcess.setApiBlacklistExemptions(mExemptions)) {
+ if (!ZYGOTE_PROCESS.setApiBlacklistExemptions(mExemptions)) {
Slog.e(TAG, "Failed to set API blacklist exemptions!");
// leave mExemptionsStr as is, so we don't try to send the same list again.
mExemptions = Collections.emptyList();
@@ -2175,7 +2164,7 @@
}
if (logSampleRate != -1 && logSampleRate != mLogSampleRate) {
mLogSampleRate = logSampleRate;
- zygoteProcess.setHiddenApiAccessLogSampleRate(mLogSampleRate);
+ ZYGOTE_PROCESS.setHiddenApiAccessLogSampleRate(mLogSampleRate);
}
mPolicy = getValidEnforcementPolicy(Settings.Global.HIDDEN_API_POLICY);
}
@@ -2864,16 +2853,18 @@
* @param userId
* @param event
* @param appToken ActivityRecord's appToken.
+ * @param taskRoot TaskRecord's root
*/
public void updateActivityUsageStats(ComponentName activity, int userId, int event,
- IBinder appToken) {
+ IBinder appToken, ComponentName taskRoot) {
if (DEBUG_SWITCH) {
Slog.d(TAG_SWITCH, "updateActivityUsageStats: comp="
+ activity + " hash=" + appToken.hashCode() + " event=" + event);
}
synchronized (this) {
if (mUsageStatsService != null) {
- mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode());
+ mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode(),
+ taskRoot);
}
}
}
@@ -2911,7 +2902,7 @@
if (mUsageStatsService != null) {
mUsageStatsService.reportEvent(service, userId,
started ? UsageEvents.Event.FOREGROUND_SERVICE_START
- : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0);
+ : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0, null);
}
}
}
@@ -4880,7 +4871,7 @@
ArraySet<String> completedIsas = new ArraySet<String>();
for (String abi : Build.SUPPORTED_ABIS) {
- zygoteProcess.establishZygoteConnectionForAbi(abi);
+ ZYGOTE_PROCESS.establishZygoteConnectionForAbi(abi);
final String instructionSet = VMRuntime.getInstructionSet(abi);
if (!completedIsas.contains(instructionSet)) {
try {
@@ -14654,8 +14645,7 @@
mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
break;
case Proxy.PROXY_CHANGE_ACTION:
- ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO);
- mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy));
+ mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG));
break;
case android.hardware.Camera.ACTION_NEW_PICTURE:
case android.hardware.Camera.ACTION_NEW_VIDEO:
@@ -17521,10 +17511,10 @@
@Override
public void updateActivityUsageStats(ComponentName activity, int userId, int event,
- IBinder appToken) {
+ IBinder appToken, ComponentName taskRoot) {
synchronized (ActivityManagerService.this) {
ActivityManagerService.this.updateActivityUsageStats(activity, userId, event,
- appToken);
+ appToken, taskRoot);
}
}
diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java
index fe27c49..fd402cc 100644
--- a/services/core/java/com/android/server/am/AppCompactor.java
+++ b/services/core/java/com/android/server/am/AppCompactor.java
@@ -64,7 +64,7 @@
final private String COMPACT_ACTION_FILE = "file";
final private String COMPACT_ACTION_ANON = "anon";
- final private String COMPACT_ACTION_FULL = "full";
+ final private String COMPACT_ACTION_FULL = "all";
final private String compactActionSome;
final private String compactActionFull;
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 65aacdc..353749f 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -45,6 +45,7 @@
import android.os.UserHandle;
import android.util.EventLog;
import android.util.Slog;
+import android.util.StatsLog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -157,6 +158,9 @@
static final int BROADCAST_INTENT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG;
static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1;
+ // log latency metrics for ordered broadcasts during BOOT_COMPLETED processing
+ boolean mLogLatencyMetrics = true;
+
final BroadcastHandler mHandler;
private final class BroadcastHandler extends Handler {
@@ -941,6 +945,12 @@
// adjustments.
mService.updateOomAdjLocked();
}
+
+ // when we have no more ordered broadcast on this queue, stop logging
+ if (mService.mUserController.mBootCompleted && mLogLatencyMetrics) {
+ mLogLatencyMetrics = false;
+ }
+
return;
}
r = mOrderedBroadcasts.get(0);
@@ -1036,6 +1046,13 @@
if (recIdx == 0) {
r.dispatchTime = r.receiverTime;
r.dispatchClockTime = System.currentTimeMillis();
+
+ if (mLogLatencyMetrics) {
+ StatsLog.write(
+ StatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED,
+ r.dispatchClockTime - r.enqueueClockTime);
+ }
+
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c1be387..003ddd1 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -145,6 +145,11 @@
static final int CACHED_APP_MAX_ADJ = 999;
static final int CACHED_APP_MIN_ADJ = 900;
+ // This is the oom_adj level that we allow to die first. This cannot be equal to
+ // CACHED_APP_MAX_ADJ unless processes are actively being assigned an oom_score_adj of
+ // CACHED_APP_MAX_ADJ.
+ static final int CACHED_APP_LMK_FIRST_ADJ = 950;
+
// Number of levels we have available for different service connection group importance
// levels.
static final int CACHED_APP_IMPORTANCE_LEVELS = 5;
@@ -266,7 +271,7 @@
// can't give it a different value for every possible kind of process.
private final int[] mOomAdj = new int[] {
FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
- BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ
+ BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_LMK_FIRST_ADJ
};
// These are the low-end OOM level limits. This is appropriate for an
// HVGA or smaller phone with less than 512MB. Values are in KB.
@@ -1497,7 +1502,7 @@
mService.mNativeDebuggingApp = null;
}
- if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PREFER_CODE_INTEGRITY) != 0
+ if (app.info.isCodeIntegrityPreferred()
|| (app.info.isPrivilegedApp()
&& DexManager.isPackageSelectedToRunOob(app.pkgList.mPkgList.keySet()))) {
runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
@@ -1730,7 +1735,7 @@
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, app.info.packageName,
- packageNames, visibleVolIds,
+ packageNames, visibleVolIds, /*useBlastulaPool=*/ false,
new String[] {PROC_START_SEQ_IDENT + app.startSeq});
} else {
startResult = Process.start(entryPoint,
@@ -2379,14 +2384,14 @@
}
@GuardedBy("mService")
- void setAllHttpProxyLocked(String host, String port, String exclList, Uri pacFileUrl) {
+ void setAllHttpProxyLocked() {
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord r = mLruProcesses.get(i);
// Don't dispatch to isolated processes as they can't access
// ConnectivityManager and don't have network privileges anyway.
if (r.thread != null && !r.isolated) {
try {
- r.thread.setHttpProxy(host, port, exclList, pacFileUrl);
+ r.thread.updateHttpProxy();
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to update http proxy for: " +
r.info.processName);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index bcce052..c981e68 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -240,6 +240,8 @@
private final LockPatternUtils mLockPatternUtils;
+ volatile boolean mBootCompleted;
+
UserController(ActivityManagerService service) {
this(new Injector(service));
}
@@ -567,6 +569,7 @@
Bundle extras, boolean ordered, boolean sticky, int sendingUser)
throws RemoteException {
Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);
+ mBootCompleted = true;
}
}, 0, null, null,
new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index f60d6b0..27edbbf 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -47,6 +47,7 @@
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.StatsLog;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -73,7 +74,6 @@
private final Context mContext;
private final PowerManager mPowerManager;
- private final ActivityManager mActivityManager;
private final Object mLock;
@GuardedBy("mLock")
private final SparseArray<UserState> mUserStates = new SparseArray<>();
@@ -85,7 +85,6 @@
super(context);
mContext = Preconditions.checkNotNull(context);
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
mLock = new Object();
mAttentionHandler = new AttentionHandler();
}
@@ -96,7 +95,7 @@
}
@Override
- public void onStopUser(int userId) {
+ public void onSwitchUser(int userId) {
cancelAndUnbindLocked(peekUserStateLocked(userId),
AttentionService.ATTENTION_FAILURE_UNKNOWN);
}
@@ -178,11 +177,16 @@
userState.mAttentionCheckCache = new AttentionCheckCache(
SystemClock.uptimeMillis(), result,
timestamp);
+
+ StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED,
+ result);
}
@Override
public void onFailure(int requestCode, int error) {
callback.onFailure(requestCode, error);
+ StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED,
+ error);
}
@Override
@@ -201,11 +205,20 @@
/** Cancels the specified attention check. */
public void cancelAttentionCheck(int requestCode) {
- final UserState userState = getOrCreateCurrentUserStateLocked();
- try {
- userState.mService.cancelAttentionCheck(requestCode);
- } catch (RemoteException e) {
- Slog.e(LOG_TAG, "Cannot call into the AttentionService");
+ synchronized (mLock) {
+ final UserState userState = getOrCreateCurrentUserStateLocked();
+ if (userState.mService == null) {
+ if (userState.mPendingAttentionCheck != null
+ && userState.mPendingAttentionCheck.mRequestCode == requestCode) {
+ userState.mPendingAttentionCheck = null;
+ }
+ return;
+ }
+ try {
+ userState.mService.cancelAttentionCheck(requestCode);
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Cannot call into the AttentionService");
+ }
}
}
@@ -224,7 +237,7 @@
@GuardedBy("mLock")
private UserState getOrCreateCurrentUserStateLocked() {
- return getOrCreateUserStateLocked(mActivityManager.getCurrentUser());
+ return getOrCreateUserStateLocked(ActivityManager.getCurrentUser());
}
@GuardedBy("mLock")
@@ -239,7 +252,7 @@
@GuardedBy("mLock")
UserState peekCurrentUserStateLocked() {
- return peekUserStateLocked(mActivityManager.getCurrentUser());
+ return peekUserStateLocked(ActivityManager.getCurrentUser());
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 11299b6..de389bc 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1223,11 +1223,13 @@
private void checkMuteAffectedStreams() {
// any stream with a min level > 0 is not muteable by definition
- // STREAM_VOICE_CALL can be muted by applications that has the the MODIFY_PHONE_STATE permission.
+ // STREAM_VOICE_CALL and STREAM_BLUETOOTH_SCO can be muted by applications
+ // that has the the MODIFY_PHONE_STATE permission.
for (int i = 0; i < mStreamStates.length; i++) {
final VolumeStreamState vss = mStreamStates[i];
if (vss.mIndexMin > 0 &&
- vss.mStreamType != AudioSystem.STREAM_VOICE_CALL) {
+ (vss.mStreamType != AudioSystem.STREAM_VOICE_CALL &&
+ vss.mStreamType != AudioSystem.STREAM_BLUETOOTH_SCO)) {
mMuteAffectedStreams &= ~(1 << vss.mStreamType);
}
}
@@ -1711,10 +1713,11 @@
return;
}
- // If adjust is mute and the stream is STREAM_VOICE_CALL, make sure
+ // If adjust is mute and the stream is STREAM_VOICE_CALL or STREAM_BLUETOOTH_SCO, make sure
// that the calling app have the MODIFY_PHONE_STATE permission.
if (isMuteAdjust &&
- streamType == AudioSystem.STREAM_VOICE_CALL &&
+ (streamType == AudioSystem.STREAM_VOICE_CALL ||
+ streamType == AudioSystem.STREAM_BLUETOOTH_SCO) &&
mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
@@ -2038,12 +2041,14 @@
+ " CHANGE_ACCESSIBILITY_VOLUME callingPackage=" + callingPackage);
return;
}
- if ((streamType == AudioManager.STREAM_VOICE_CALL) &&
+ if ((streamType == AudioManager.STREAM_VOICE_CALL ||
+ streamType == AudioManager.STREAM_BLUETOOTH_SCO) &&
(index == 0) &&
(mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED)) {
- Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL and index 0 without"
+ Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL or"
+ + " STREAM_BLUETOOTH_SCO and index 0 without"
+ " MODIFY_PHONE_STATE callingPackage=" + callingPackage);
return;
}
@@ -4746,12 +4751,18 @@
private int getA2dpCodec(BluetoothDevice device) {
synchronized (mA2dpAvrcpLock) {
- if (mA2dp != null) {
- BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
- BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
- return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
+ if (mA2dp == null) {
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
}
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
+ if (btCodecStatus == null) {
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ }
+ BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
+ if (btCodecConfig == null) {
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ }
+ return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
}
}
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index ac74598..79b56c6 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -20,7 +20,6 @@
import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics;
import android.net.INetdEventCallback;
-import android.net.ip.IpClient;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.IpConnectivityLog;
import android.os.Binder;
@@ -270,8 +269,6 @@
// Dump the rolling buffer of metrics event and pretty print events using a human readable
// format. Also print network dns/connect statistics and default network event time series.
static final String CMD_LIST = "list";
- // Dump all IpClient logs ("ipclient").
- static final String CMD_IPCLIENT = IpClient.DUMP_ARG;
// By default any other argument will fall into the default case which is the equivalent
// of calling both the "list" and "ipclient" commands. This includes most notably bug
// reports collected by dumpsys.cpp with the "-a" argument.
@@ -295,20 +292,9 @@
case CMD_PROTO:
cmdListAsProto(pw);
return;
- case CMD_IPCLIENT: {
- final String[] ipclientArgs = ((args != null) && (args.length > 1))
- ? Arrays.copyOfRange(args, 1, args.length)
- : null;
- IpClient.dumpAllLogs(pw, ipclientArgs);
- return;
- }
case CMD_LIST:
- cmdList(pw);
- return;
default:
cmdList(pw);
- pw.println("");
- IpClient.dumpAllLogs(pw, null);
return;
}
}
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 8a3cdca..1559ba8 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -16,40 +16,49 @@
package com.android.server.connectivity;
-import com.android.internal.util.HexDump;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.connectivity.NetworkAgentInfo;
-import android.net.ConnectivityManager;
+// TODO: Clean up imports and remove references of PacketKeepalive constants.
+
+import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_INTERVAL;
+import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_IP_ADDRESS;
+import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK;
+import static android.net.ConnectivityManager.PacketKeepalive.MIN_INTERVAL;
+import static android.net.ConnectivityManager.PacketKeepalive.NATT_PORT;
+import static android.net.ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
+import static android.net.ConnectivityManager.PacketKeepalive.SUCCESS;
+import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE;
+import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE;
+import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE;
+import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.net.ConnectivityManager.PacketKeepalive;
import android.net.KeepalivePacketData;
-import android.net.LinkAddress;
import android.net.NetworkAgent;
import android.net.NetworkUtils;
import android.net.util.IpUtils;
import android.os.Binder;
-import android.os.IBinder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.Process;
import android.os.RemoteException;
-import android.system.OsConstants;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.Log;
import android.util.Pair;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
+import com.android.internal.util.HexDump;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
-import static android.net.ConnectivityManager.PacketKeepalive.*;
-import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE;
-import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE;
-import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE;
-
/**
* Manages packet keepalive requests.
*
@@ -300,7 +309,9 @@
}
}
- public void handleEventPacketKeepalive(NetworkAgentInfo nai, Message message) {
+ /** Handle keepalive events from lower layer. */
+ public void handleEventPacketKeepalive(@NonNull NetworkAgentInfo nai,
+ @NonNull Message message) {
int slot = message.arg1;
int reason = message.arg2;
@@ -330,8 +341,18 @@
}
}
- public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger,
- IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) {
+ /**
+ * Called when requesting that keepalives be started on a IPsec NAT-T socket. See
+ * {@link android.net.SocketKeepalive}.
+ **/
+ public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+ int intervalSeconds,
+ @NonNull Messenger messenger,
+ @NonNull IBinder binder,
+ @NonNull String srcAddrString,
+ int srcPort,
+ @NonNull String dstAddrString,
+ int dstPort) {
if (nai == null) {
notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK);
return;
@@ -360,6 +381,56 @@
NetworkAgent.CMD_START_PACKET_KEEPALIVE, ki).sendToTarget();
}
+ /**
+ * Called when requesting that keepalives be started on a IPsec NAT-T socket. This function is
+ * identical to {@link #startNattKeepalive}, but also takes a {@code resourceId}, which is the
+ * resource index bound to the {@link UdpEncapsulationSocket} when creating by
+ * {@link com.android.server.IpSecService} to verify whether the given
+ * {@link UdpEncapsulationSocket} is legitimate.
+ **/
+ public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+ @Nullable FileDescriptor fd,
+ int resourceId,
+ int intervalSeconds,
+ @NonNull Messenger messenger,
+ @NonNull IBinder binder,
+ @NonNull String srcAddrString,
+ @NonNull String dstAddrString,
+ int dstPort) {
+ // Ensure that the socket is created by IpSecService.
+ if (!isNattKeepaliveSocketValid(fd, resourceId)) {
+ notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET);
+ }
+
+ // Get src port to adopt old API.
+ int srcPort = 0;
+ try {
+ final SocketAddress srcSockAddr = Os.getsockname(fd);
+ srcPort = ((InetSocketAddress) srcSockAddr).getPort();
+ } catch (ErrnoException e) {
+ notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET);
+ }
+
+ // Forward request to old API.
+ startNattKeepalive(nai, intervalSeconds, messenger, binder, srcAddrString, srcPort,
+ dstAddrString, dstPort);
+ }
+
+ /**
+ * Verify if the IPsec NAT-T file descriptor and resource Id hold for IPsec keepalive is valid.
+ **/
+ public static boolean isNattKeepaliveSocketValid(@Nullable FileDescriptor fd, int resourceId) {
+ // TODO: 1. confirm whether the fd is called from system api or created by IpSecService.
+ // 2. If the fd is created from the system api, check that it's bounded. And
+ // call dup to keep the fd open.
+ // 3. If the fd is created from IpSecService, check if the resource ID is valid. And
+ // hold the resource needed in IpSecService.
+ if (null == fd) {
+ return false;
+ }
+ return true;
+ }
+
public void dump(IndentingPrintWriter pw) {
pw.println("Packet keepalives:");
pw.increaseIndent();
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 9ea73fb..d0cff25 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -152,6 +152,10 @@
// Whether a captive portal was found during the last network validation attempt.
public boolean lastCaptivePortalDetected;
+ // Indicates the user was notified of a successful captive portal login since a portal was
+ // last detected.
+ public boolean captivePortalLoginNotified;
+
// Networks are lingered when they become unneeded as a result of their NetworkRequests being
// satisfied by a higher-scoring network. so as to allow communication to wrap up before the
// network is taken down. This usually only happens to the default network. Lingering ends with
@@ -618,18 +622,19 @@
}
public String toString() {
- return "NetworkAgentInfo{ ni{" + networkInfo + "} " +
- "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} " +
- "lp{" + linkProperties + "} " +
- "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} " +
- "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " +
- "created{" + created + "} lingering{" + isLingering() + "} " +
- "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
- "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
- "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " +
- "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " +
- "clat{" + clatd + "} " +
- "}";
+ return "NetworkAgentInfo{ ni{" + networkInfo + "} "
+ + "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} "
+ + "lp{" + linkProperties + "} "
+ + "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} "
+ + "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} "
+ + "created{" + created + "} lingering{" + isLingering() + "} "
+ + "explicitlySelected{" + networkMisc.explicitlySelected + "} "
+ + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} "
+ + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
+ + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
+ + "captivePortalLoginNotified{" + captivePortalLoginNotified + "} "
+ + "clat{" + clatd + "} "
+ + "}";
}
public String name() {
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 36a2476..b50477b 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -16,13 +16,16 @@
package com.android.server.connectivity;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.net.NetworkCapabilities;
import android.net.wifi.WifiInfo;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
@@ -31,15 +34,12 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.widget.Toast;
+
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-
public class NetworkNotificationManager {
@@ -47,7 +47,8 @@
LOST_INTERNET(SystemMessage.NOTE_NETWORK_LOST_INTERNET),
NETWORK_SWITCH(SystemMessage.NOTE_NETWORK_SWITCH),
NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET),
- SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN);
+ SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN),
+ LOGGED_IN(SystemMessage.NOTE_NETWORK_LOGGED_IN);
public final int eventId;
@@ -192,6 +193,9 @@
details = r.getString(R.string.network_available_sign_in_detailed, name);
break;
}
+ } else if (notifyType == NotificationType.LOGGED_IN) {
+ title = WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID());
+ details = r.getString(R.string.captive_portal_logged_in_detailed);
} else if (notifyType == NotificationType.NETWORK_SWITCH) {
String fromTransport = getTransportName(transportType);
String toTransport = getTransportName(getFirstTransportType(switchToNai));
@@ -239,6 +243,18 @@
}
}
+ /**
+ * Clear the notification with the given id, only if it matches the given type.
+ */
+ public void clearNotification(int id, NotificationType notifyType) {
+ final int previousEventId = mNotificationTypeMap.get(id);
+ final NotificationType previousNotifyType = NotificationType.getFromId(previousEventId);
+ if (notifyType != previousNotifyType) {
+ return;
+ }
+ clearNotification(id);
+ }
+
public void clearNotification(int id) {
if (mNotificationTypeMap.indexOfKey(id) < 0) {
return;
@@ -290,6 +306,10 @@
return (t != null) ? t.name() : "UNKNOWN";
}
+ /**
+ * A notification with a higher number will take priority over a notification with a lower
+ * number.
+ */
private static int priority(NotificationType t) {
if (t == null) {
return 0;
@@ -302,6 +322,7 @@
case NETWORK_SWITCH:
return 2;
case LOST_INTERNET:
+ case LOGGED_IN:
return 1;
default:
return 0;
diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java
index fdddccd..a671287 100644
--- a/services/core/java/com/android/server/connectivity/ProxyTracker.java
+++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java
@@ -309,22 +309,4 @@
}
}
}
-
- /**
- * Enable or disable the default proxy.
- *
- * This sets the flag for enabling/disabling the default proxy and sends the broadcast
- * if applicable.
- * @param enabled whether the default proxy should be enabled.
- */
- public void setDefaultProxyEnabled(final boolean enabled) {
- synchronized (mProxyLock) {
- if (mDefaultProxyEnabled != enabled) {
- mDefaultProxyEnabled = enabled;
- if (mGlobalProxy == null && mDefaultProxy != null) {
- sendProxyBroadcast();
- }
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 602aedb..62a1b03 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -60,7 +60,6 @@
import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.UidRange;
-import android.net.Uri;
import android.net.VpnService;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
@@ -71,7 +70,6 @@
import android.os.Looper;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
-import android.os.PatternMatcher;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -100,6 +98,8 @@
import com.android.server.LocalServices;
import com.android.server.net.BaseNetworkObserver;
+import libcore.io.IoUtils;
+
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -121,8 +121,6 @@
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
-import libcore.io.IoUtils;
-
/**
* @hide
*/
@@ -346,11 +344,18 @@
*
* @return {@code true} if VPN lockdown is enabled.
*/
- public boolean getLockdown() {
+ public synchronized boolean getLockdown() {
return mLockdown;
}
/**
+ * Returns whether VPN is configured as always-on.
+ */
+ public synchronized boolean getAlwaysOn() {
+ return mAlwaysOn;
+ }
+
+ /**
* Checks if a VPN app supports always-on mode.
*
* In order to support the always-on feature, an app has to
@@ -788,6 +793,8 @@
}
}
+ lp.setHttpProxy(mConfig.proxyInfo);
+
if (!allowIPv4) {
lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
}
diff --git a/services/core/java/com/android/server/display/ColorDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java
index 9223739..3a58160 100644
--- a/services/core/java/com/android/server/display/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/ColorDisplayService.java
@@ -28,6 +28,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
+import android.annotation.UserIdInt;
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -51,6 +52,7 @@
import android.provider.Settings.System;
import android.util.MathUtils;
import android.util.Slog;
+import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AnimationUtils;
@@ -866,6 +868,12 @@
if (mDisplayWhiteBalanceListener != null && oldActivated != activated) {
mDisplayWhiteBalanceListener.onDisplayWhiteBalanceStatusChanged(activated);
}
+
+ // If disabled, clear the tint. If enabled, do nothing more here and let the next
+ // temperature update set the correct tint.
+ if (!activated) {
+ applyTint(mDisplayWhiteBalanceTintController, false);
+ }
}
private boolean isDisplayWhiteBalanceSettingEnabled() {
@@ -878,6 +886,21 @@
return dtm.isDeviceColorManaged();
}
+ private int getTransformCapabilitiesInternal() {
+ int availabilityFlags = ColorDisplayManager.CAPABILITY_NONE;
+ if (SurfaceControl.getProtectedContentSupport()) {
+ availabilityFlags |= ColorDisplayManager.CAPABILITY_PROTECTED_CONTENT;
+ }
+ final Resources res = getContext().getResources();
+ if (res.getBoolean(R.bool.config_setColorTransformAccelerated)) {
+ availabilityFlags |= ColorDisplayManager.CAPABILITY_HARDWARE_ACCELERATION_GLOBAL;
+ }
+ if (res.getBoolean(R.bool.config_setColorTransformAcceleratedPerLayer)) {
+ availabilityFlags |= ColorDisplayManager.CAPABILITY_HARDWARE_ACCELERATION_PER_APP;
+ }
+ return availabilityFlags;
+ }
+
/**
* Returns the last time the night display transform activation state was changed, or {@link
* LocalDateTime#MIN} if night display has never been activated.
@@ -1226,10 +1249,10 @@
* Adds a {@link WeakReference<ColorTransformController>} for a newly started activity, and
* invokes {@link ColorTransformController#applyAppSaturation(float[], float[])} if needed.
*/
- public boolean attachColorTransformController(String packageName, int uid,
+ public boolean attachColorTransformController(String packageName, @UserIdInt int userId,
WeakReference<ColorTransformController> controller) {
return mAppSaturationController
- .addColorTransformController(packageName, uid, controller);
+ .addColorTransformController(packageName, userId, controller);
}
}
@@ -1318,6 +1341,18 @@
}
}
+ public int getTransformCapabilities() {
+ getContext().enforceCallingPermission(
+ Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+ "Permission required to query transform capabilities");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getTransformCapabilitiesInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 63214ed..cac1a95 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -268,7 +268,7 @@
mTvSystemAudioModeSupport = false;
// Record the last state of System Audio Control before going to standby
synchronized (mLock) {
- mService.writeStringSetting(
+ mService.writeStringSystemProperty(
Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL,
mSystemAudioActivated ? "true" : "false");
}
@@ -330,7 +330,7 @@
@ServiceThreadOnly
protected void setPreferredAddress(int addr) {
assertRunOnServiceThread();
- mService.writeStringSetting(
+ mService.writeStringSystemProperty(
Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, String.valueOf(addr));
}
@@ -469,7 +469,7 @@
protected boolean handleRequestArcInitiate(HdmiCecMessage message) {
assertRunOnServiceThread();
removeAction(ArcInitiationActionFromAvr.class);
- if (!mService.readBooleanSetting(Constants.PROPERTY_ARC_SUPPORT, true)) {
+ if (!mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) {
mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
} else if (!isDirectConnectToTv()) {
HdmiLogger.debug("AVR device is not directly connected with TV");
@@ -829,7 +829,7 @@
boolean currentMuteStatus =
mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
if (currentMuteStatus == newSystemAudioMode) {
- if (mService.readBooleanSetting(
+ if (mService.readBooleanSystemProperty(
Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, true)
|| newSystemAudioMode) {
mService.getAudioManager()
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 7a0c279..ef7d241 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -100,7 +100,7 @@
@ServiceThreadOnly
protected void setPreferredAddress(int addr) {
assertRunOnServiceThread();
- mService.writeStringSetting(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
+ mService.writeStringSystemProperty(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
String.valueOf(addr));
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 46219d5..f3a1e46 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -657,9 +657,13 @@
Global.putInt(cr, key, toInt(value));
}
- void writeStringSetting(String key, String value) {
- ContentResolver cr = getContext().getContentResolver();
- Global.putString(cr, key, value);
+ void writeStringSystemProperty(String key, String value) {
+ SystemProperties.set(key, value);
+ }
+
+ @VisibleForTesting
+ boolean readBooleanSystemProperty(String key, boolean defVal) {
+ return SystemProperties.getBoolean(key, defVal);
}
private void initializeCec(int initiatedBy) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index d4b8eb2..944a95d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.content.ComponentName;
import android.view.inputmethod.InputMethodInfo;
import com.android.server.LocalServices;
@@ -42,11 +41,6 @@
public abstract void hideCurrentInputMethod();
/**
- * Switches to VR InputMethod defined in the packageName of {@param componentName}.
- */
- public abstract void startVrInputMethodNoCheck(ComponentName componentName);
-
- /**
* Returns the list of installed input methods for the specified user.
*
* @param userId The user ID to be queried.
@@ -76,10 +70,6 @@
}
@Override
- public void startVrInputMethodNoCheck(ComponentName componentName) {
- }
-
- @Override
public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
return Collections.emptyList();
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2d197bb..3fd3945 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -91,8 +91,6 @@
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
-import android.service.vr.IVrManager;
-import android.service.vr.IVrStateCallbacks;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.util.ArrayMap;
@@ -203,7 +201,6 @@
static final int MSG_CREATE_SESSION = 1050;
static final int MSG_START_INPUT = 2000;
- static final int MSG_START_VR_INPUT = 2010;
static final int MSG_UNBIND_CLIENT = 3000;
static final int MSG_BIND_CLIENT = 3010;
@@ -381,28 +378,6 @@
}
}
- /**
- * VR state callback.
- * Listens for when VR mode finishes.
- */
- private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
- @Override
- public void onVrStateChanged(boolean enabled) {
- if (!enabled) {
- restoreNonVrImeFromSettingsNoCheck();
- }
- }
- };
-
- private void restoreNonVrImeFromSettingsNoCheck() {
- // switch back to non-VR InputMethod from settings.
- synchronized (mMethodMap) {
- if (!mIsVrImeStarted) return;
- mIsVrImeStarted = false;
- updateFromSettingsLocked(false);
- }
- }
-
private static final class ClientDeathRecipient implements IBinder.DeathRecipient {
private final InputMethodManagerService mImms;
private final IInputMethodClient mClient;
@@ -597,9 +572,6 @@
final ImeDisplayValidator mImeDisplayValidator;
- /** True if VR IME started by {@link #startVrInputMethodNoCheck}. */
- boolean mIsVrImeStarted;
-
/**
* If non-null, this is the input method service we are currently connected
* to.
@@ -977,31 +949,6 @@
}
/**
- * Start a VR InputMethod that matches IME with package name of {@param component}.
- * Note: This method is called from {@link android.app.VrManager}.
- */
- private void startVrInputMethodNoCheck(@Nullable ComponentName component) {
- if (component == null) {
- // clear the current VR-only IME (if any) and restore normal IME.
- restoreNonVrImeFromSettingsNoCheck();
- return;
- }
-
- synchronized (mMethodMap) {
- String packageName = component.getPackageName();
- for (InputMethodInfo info : mMethodList) {
- if (TextUtils.equals(info.getPackageName(), packageName) && info.isVrOnly()) {
- // set this is as current inputMethod without updating settings.
- setInputMethodEnabledLocked(info.getId(), true);
- setInputMethodLocked(info.getId(), NOT_A_SUBTYPE_ID);
- mIsVrImeStarted = true;
- break;
- }
- }
- }
- }
-
- /**
* Handles {@link Intent#ACTION_LOCALE_CHANGED}.
*
* <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
@@ -1452,15 +1399,6 @@
AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
mSettings, context);
- // Register VR-state listener.
- IVrManager vrManager = (IVrManager) ServiceManager.getService(Context.VR_SERVICE);
- if (vrManager != null) {
- try {
- vrManager.registerListener(mVrStateCallbacks);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to register VR mode state listener.");
- }
- }
}
private void resetDefaultImeLocked(Context context) {
@@ -1607,7 +1545,7 @@
// 1) it comes from the system process
// 2) the calling process' user id is identical to the current user id IMMS thinks.
@GuardedBy("mMethodMap")
- private boolean calledFromValidUserLocked(boolean allowCrossProfileAccess) {
+ private boolean calledFromValidUserLocked() {
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(uid);
if (DEBUG) {
@@ -1623,7 +1561,7 @@
if (userId == mSettings.getCurrentUserId()) {
return true;
}
- if (allowCrossProfileAccess && mSettings.isCurrentProfile(userId)) {
+ if (!PER_PROFILE_IME_ENABLED && mSettings.isCurrentProfile(userId)) {
return true;
}
@@ -1688,25 +1626,7 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- return getInputMethodListLocked(false /* isVrOnly */, resolvedUserIds[0]);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- @Override
- public List<InputMethodInfo> getVrInputMethodList() {
- final int callingUserId = UserHandle.getCallingUserId();
- synchronized (mMethodMap) {
- final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
- mSettings.getCurrentUserId(), null);
- if (resolvedUserIds.length != 1) {
- return Collections.emptyList();
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- return getInputMethodListLocked(true /* isVrOnly */, resolvedUserIds[0]);
+ return getInputMethodListLocked(resolvedUserIds[0]);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1732,8 +1652,7 @@
}
@GuardedBy("mMethodMap")
- private List<InputMethodInfo> getInputMethodListLocked(boolean isVrOnly,
- @UserIdInt int userId) {
+ private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId) {
final ArrayList<InputMethodInfo> methodList;
if (userId == mSettings.getCurrentUserId()) {
// Create a copy.
@@ -1747,7 +1666,6 @@
queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
methodList);
}
- methodList.removeIf(imi -> imi.isVrOnly() != isVrOnly);
return methodList;
}
@@ -2021,8 +1939,8 @@
}
// Compute the final shown display ID with validated cs.selfReportedDisplayId for this
// session & other conditions.
- final int displayIdToShowIme = computeImeDisplayIdForTarget(
- cs.selfReportedDisplayId, mIsVrImeStarted, mImeDisplayValidator);
+ final int displayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId,
+ mImeDisplayValidator);
if (mCurClient != cs) {
// Was the keyguard locked when switching over to the new client?
@@ -2131,17 +2049,11 @@
* Find the display where the IME should be shown.
*
* @param displayId the ID of the display where the IME client target is.
- * @param isVrImeStarted {@code true} if VR IME started, {@code false} otherwise.
* @param checker instance of {@link ImeDisplayValidator} which is used for
* checking display config to adjust the final target display.
* @return The ID of the display where the IME should be shown.
*/
- static int computeImeDisplayIdForTarget(int displayId, boolean isVrImeStarted,
- @NonNull ImeDisplayValidator checker) {
- // For VR IME, we always show in default display.
- if (isVrImeStarted) {
- return DEFAULT_DISPLAY;
- }
+ static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) {
if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) {
// We always assume that the default display id suitable to show the IME window.
return DEFAULT_DISPLAY;
@@ -2651,7 +2563,7 @@
ResultReceiver resultReceiver) {
int uid = Binder.getCallingUid();
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+ if (!calledFromValidUserLocked()) {
return false;
}
final long ident = Binder.clearCallingIdentity();
@@ -2736,7 +2648,7 @@
ResultReceiver resultReceiver) {
int uid = Binder.getCallingUid();
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+ if (!calledFromValidUserLocked()) {
return false;
}
final long ident = Binder.clearCallingIdentity();
@@ -3087,7 +2999,7 @@
public void showInputMethodPickerFromClient(
IInputMethodClient client, int auxiliarySubtypeMode) {
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+ if (!calledFromValidUserLocked()) {
return;
}
if(!canShowInputMethodPickerLocked(client)) {
@@ -3159,7 +3071,7 @@
IInputMethodClient client, String inputMethodId) {
synchronized (mMethodMap) {
// TODO(yukawa): Should we verify the display ID?
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+ if (!calledFromValidUserLocked()) {
return;
}
executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
@@ -3274,7 +3186,7 @@
@Override
public InputMethodSubtype getLastInputMethodSubtype() {
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+ if (!calledFromValidUserLocked()) {
return null;
}
final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
@@ -3312,7 +3224,7 @@
}
}
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+ if (!calledFromValidUserLocked()) {
return;
}
if (!mSystemReady) {
@@ -3627,9 +3539,6 @@
case MSG_SET_INTERACTIVE:
handleSetInteractive(msg.arg1 != 0);
return true;
- case MSG_START_VR_INPUT:
- startVrInputMethodNoCheck((ComponentName) msg.obj);
- return true;
case MSG_REPORT_FULLSCREEN_MODE: {
final boolean fullscreen = msg.arg1 != 0;
final ClientState clientState = (ClientState)msg.obj;
@@ -3716,6 +3625,9 @@
try {
final InputMethodInfo imi = new InputMethodInfo(context, ri,
additionalSubtypeMap.get(imeId));
+ if (imi.isVrOnly()) {
+ continue; // Skip VR-only IME, which isn't supported for now.
+ }
methodList.add(imi);
methodMap.put(imi.getId(), imi);
if (DEBUG) {
@@ -4099,17 +4011,7 @@
private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
boolean setSubtypeOnly) {
- // Updates to InputMethod are transient in VR mode. Its not included in history.
- final boolean isVrInput = imi != null && imi.isVrOnly();
- if (!isVrInput) {
- // Update the history of InputMethod and Subtype
- mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
- }
-
- if (isVrInput) {
- // Updates to InputMethod are transient in VR mode. Any changes to Settings are skipped.
- return;
- }
+ mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
// Set Subtype here
if (imi == null || subtypeId < 0) {
@@ -4158,7 +4060,7 @@
public InputMethodSubtype getCurrentInputMethodSubtype() {
synchronized (mMethodMap) {
// TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+ if (!calledFromValidUserLocked()) {
return null;
}
return getCurrentInputMethodSubtypeLocked();
@@ -4204,28 +4106,9 @@
return mCurrentSubtype;
}
- @Override
- public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
- synchronized (mMethodMap) {
- // TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
- return false;
- }
- if (subtype != null && mCurMethodId != null) {
- InputMethodInfo imi = mMethodMap.get(mCurMethodId);
- int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode());
- if (subtypeId != NOT_A_SUBTYPE_ID) {
- setInputMethodLocked(mCurMethodId, subtypeId);
- return true;
- }
- }
- return false;
- }
- }
-
private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
synchronized (mMethodMap) {
- return getInputMethodListLocked(false, userId);
+ return getInputMethodListLocked(userId);
}
}
@@ -4257,11 +4140,6 @@
}
@Override
- public void startVrInputMethodNoCheck(@Nullable ComponentName componentName) {
- mService.mHandler.obtainMessage(MSG_START_VR_INPUT, componentName).sendToTarget();
- }
-
- @Override
public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
return mService.getInputMethodListAsUser(userId);
}
@@ -4648,7 +4526,7 @@
mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
for (int userId : userIds) {
final List<InputMethodInfo> methods = all
- ? getInputMethodListLocked(false, userId)
+ ? getInputMethodListLocked(userId)
: getEnabledInputMethodListLocked(userId);
if (userIds.length > 1) {
pr.print("User #");
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 6f0c5e8..3222143 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -159,11 +159,6 @@
}
@Override
- public void startVrInputMethodNoCheck(ComponentName componentName) {
- reportNotSupported();
- }
-
- @Override
public List<InputMethodInfo> getInputMethodListAsUser(
@UserIdInt int userId) {
return userIdToInputMethodInfoMapper.getAsList(userId);
@@ -1244,13 +1239,6 @@
@BinderThread
@Override
- public List<InputMethodInfo> getVrInputMethodList() {
- reportNotSupported();
- return Collections.emptyList();
- }
-
- @BinderThread
- @Override
public List<InputMethodInfo> getEnabledInputMethodList() {
return mInputMethodInfoMap.getAsList(UserHandle.getUserId(Binder.getCallingUid()));
}
@@ -1531,13 +1519,6 @@
@BinderThread
@Override
- public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
- reportNotSupported();
- return false;
- }
-
- @BinderThread
- @Override
public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
reportNotSupported();
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index cc0ac9a..bd12075 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -443,6 +443,16 @@
"qc_window_size_rare_ms";
private static final String KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
"qc_max_execution_time_ms";
+ private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE =
+ "qc_max_job_count_active";
+ private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING =
+ "qc_max_job_count_working";
+ private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT =
+ "qc_max_job_count_frequent";
+ private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE =
+ "qc_max_job_count_rare";
+ private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME =
+ "qc_max_count_per_allowed_time";
private static final int DEFAULT_MIN_IDLE_COUNT = 1;
private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
@@ -479,6 +489,15 @@
24 * 60 * 60 * 1000L; // 24 hours
private static final long DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
4 * 60 * 60 * 1000L; // 4 hours
+ private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE =
+ 200; // 1200/hr
+ private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING =
+ 1200; // 600/hr
+ private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT =
+ 1800; // 225/hr
+ private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE =
+ 2400; // 100/hr
+ private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 20;
/**
* Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
@@ -682,6 +701,41 @@
public long QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS;
+ /**
+ * The maximum number of jobs an app can run within this particular standby bucket's
+ * window size.
+ */
+ public int QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE =
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE;
+
+ /**
+ * The maximum number of jobs an app can run within this particular standby bucket's
+ * window size.
+ */
+ public int QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING =
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING;
+
+ /**
+ * The maximum number of jobs an app can run within this particular standby bucket's
+ * window size.
+ */
+ public int QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT =
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT;
+
+ /**
+ * The maximum number of jobs an app can run within this particular standby bucket's
+ * window size.
+ */
+ public int QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE =
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE;
+
+ /**
+ * The maximum number of jobs that can run within the past
+ * {@link #QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}.
+ */
+ public int QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME =
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
+
private final KeyValueListParser mParser = new KeyValueListParser(',');
void updateConstantsLocked(String value) {
@@ -767,6 +821,21 @@
QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = mParser.getDurationMillis(
KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS,
DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS);
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = mParser.getInt(
+ KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE,
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE);
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = mParser.getInt(
+ KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING,
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING);
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = mParser.getInt(
+ KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT,
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT);
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = mParser.getInt(
+ KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE,
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE);
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = mParser.getInt(
+ KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME,
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME);
}
void dump(IndentingPrintWriter pw) {
@@ -823,6 +892,16 @@
QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS).println();
pw.printPair(KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS,
QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS).println();
+ pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE).println();
+ pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING).println();
+ pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT).println();
+ pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE).println();
+ pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME).println();
pw.decreaseIndent();
}
@@ -872,6 +951,16 @@
QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS);
proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS,
QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS);
+ proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_ACTIVE,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE);
+ proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_WORKING,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING);
+ proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT);
+ proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE);
+ proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_ALLOWED_TIME,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME);
proto.end(qcToken);
proto.end(token);
diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java
index c16d1b4..5a0b991 100644
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ b/services/core/java/com/android/server/job/controllers/QuotaController.java
@@ -230,10 +230,10 @@
@VisibleForTesting
static class ExecutionStats {
/**
- * The time at which this record should be considered invalid, in the elapsed realtime
- * timebase.
+ * The time after which this record should be considered invalid (out of date), in the
+ * elapsed realtime timebase.
*/
- public long invalidTimeElapsed;
+ public long expirationTimeElapsed;
public long windowSizeMs;
@@ -241,29 +241,45 @@
public long executionTimeInWindowMs;
public int bgJobCountInWindow;
- /** The total amount of time the app ran in the last {@link MAX_PERIOD_MS}. */
+ /** The total amount of time the app ran in the last {@link #MAX_PERIOD_MS}. */
public long executionTimeInMaxPeriodMs;
public int bgJobCountInMaxPeriod;
/**
- * The time after which the sum of all the app's sessions plus {@link mQuotaBufferMs} equals
- * the quota. This is only valid if
- * executionTimeInWindowMs >= {@link mAllowedTimePerPeriodMs} or
- * executionTimeInMaxPeriodMs >= {@link mMaxExecutionTimeMs}.
+ * The time after which the sum of all the app's sessions plus {@link #mQuotaBufferMs}
+ * equals the quota. This is only valid if
+ * executionTimeInWindowMs >= {@link #mAllowedTimePerPeriodMs} or
+ * executionTimeInMaxPeriodMs >= {@link #mMaxExecutionTimeMs}.
*/
public long quotaCutoffTimeElapsed;
+ /**
+ * The time after which {@link #jobCountInAllowedTime} should be considered invalid, in the
+ * elapsed realtime timebase.
+ */
+ public long jobCountExpirationTimeElapsed;
+
+ /**
+ * The number of jobs that ran in at least the last {@link #mAllowedTimePerPeriodMs}.
+ * It may contain a few stale entries since cleanup won't happen exactly every
+ * {@link #mAllowedTimePerPeriodMs}.
+ */
+ public int jobCountInAllowedTime;
+
@Override
public String toString() {
return new StringBuilder()
- .append("invalidTime=").append(invalidTimeElapsed).append(", ")
+ .append("expirationTime=").append(expirationTimeElapsed).append(", ")
.append("windowSize=").append(windowSizeMs).append(", ")
.append("executionTimeInWindow=").append(executionTimeInWindowMs).append(", ")
.append("bgJobCountInWindow=").append(bgJobCountInWindow).append(", ")
.append("executionTimeInMaxPeriod=").append(executionTimeInMaxPeriodMs)
.append(", ")
.append("bgJobCountInMaxPeriod=").append(bgJobCountInMaxPeriod).append(", ")
- .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed)
+ .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed).append(", ")
+ .append("jobCountExpirationTime").append(jobCountExpirationTimeElapsed)
+ .append(", ")
+ .append("jobCountInAllowedTime").append(jobCountInAllowedTime)
.toString();
}
@@ -271,13 +287,15 @@
public boolean equals(Object obj) {
if (obj instanceof ExecutionStats) {
ExecutionStats other = (ExecutionStats) obj;
- return this.invalidTimeElapsed == other.invalidTimeElapsed
+ return this.expirationTimeElapsed == other.expirationTimeElapsed
&& this.windowSizeMs == other.windowSizeMs
&& this.executionTimeInWindowMs == other.executionTimeInWindowMs
&& this.bgJobCountInWindow == other.bgJobCountInWindow
&& this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs
&& this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod
- && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed;
+ && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed
+ && this.jobCountExpirationTimeElapsed == other.jobCountExpirationTimeElapsed
+ && this.jobCountInAllowedTime == other.jobCountInAllowedTime;
} else {
return false;
}
@@ -286,13 +304,15 @@
@Override
public int hashCode() {
int result = 0;
- result = 31 * result + hashLong(invalidTimeElapsed);
+ result = 31 * result + hashLong(expirationTimeElapsed);
result = 31 * result + hashLong(windowSizeMs);
result = 31 * result + hashLong(executionTimeInWindowMs);
result = 31 * result + bgJobCountInWindow;
result = 31 * result + hashLong(executionTimeInMaxPeriodMs);
result = 31 * result + bgJobCountInMaxPeriod;
result = 31 * result + hashLong(quotaCutoffTimeElapsed);
+ result = 31 * result + hashLong(jobCountExpirationTimeElapsed);
+ result = 31 * result + jobCountInAllowedTime;
return result;
}
}
@@ -320,7 +340,7 @@
/**
* List of jobs that started while the UID was in the TOP state. There will be no more than
- * 16 ({@link JobSchedulerService.MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
+ * 16 ({@link JobSchedulerService#MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
* fine.
*/
private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
@@ -343,7 +363,7 @@
private long mAllowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
/**
- * The maximum amount of time an app can have its jobs running within a {@link MAX_PERIOD_MS}
+ * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS}
* window.
*/
private long mMaxExecutionTimeMs = 4 * 60 * MINUTE_IN_MILLIS;
@@ -355,17 +375,20 @@
private long mQuotaBufferMs = 30 * 1000L; // 30 seconds
/**
- * {@link mAllowedTimePerPeriodMs} - {@link mQuotaBufferMs}. This can be used to determine when
- * an app will have enough quota to transition from out-of-quota to in-quota.
+ * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine
+ * when an app will have enough quota to transition from out-of-quota to in-quota.
*/
private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
/**
- * {@link mMaxExecutionTimeMs} - {@link mQuotaBufferMs}. This can be used to determine when an
+ * {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an
* app will have enough quota to transition from out-of-quota to in-quota.
*/
private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+ /** The maximum number of jobs that can run within the past {@link #mAllowedTimePerPeriodMs}. */
+ private int mMaxJobCountPerAllowedTime = 20;
+
private long mNextCleanupTimeElapsed = 0;
private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener =
new AlarmManager.OnAlarmListener() {
@@ -412,6 +435,23 @@
/** The maximum period any bucket can have. */
private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS;
+ /**
+ * The maximum number of jobs based on its standby bucket. For each max value count in the
+ * array, the app will not be allowed to run more than that many number of jobs within the
+ * latest time interval of its rolling window size.
+ *
+ * @see #mBucketPeriodsMs
+ */
+ private final int[] mMaxBucketJobCounts = new int[] {
+ 200, // ACTIVE -- 1200/hr
+ 1200, // WORKING -- 600/hr
+ 1800, // FREQUENT -- 225/hr
+ 2400 // RARE -- 100/hr
+ };
+
+ /** The minimum number of jobs that any bucket will be allowed to run. */
+ private static final int MIN_BUCKET_JOB_COUNT = 100;
+
/** An app has reached its quota. The message should contain a {@link Package} object. */
private static final int MSG_REACHED_QUOTA = 0;
/** Drop any old timing sessions. */
@@ -463,17 +503,21 @@
@Override
public void prepareForExecutionLocked(JobStatus jobStatus) {
if (DEBUG) Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
+
+ final int uid = jobStatus.getSourceUid();
+ if (mActivityManagerInternal.getUidProcessState(uid) <= ActivityManager.PROCESS_STATE_TOP) {
+ mTopStartedJobs.add(jobStatus);
+ // Top jobs won't count towards quota so there's no need to involve the Timer.
+ return;
+ }
+
final int userId = jobStatus.getSourceUserId();
final String packageName = jobStatus.getSourcePackageName();
- final int uid = jobStatus.getSourceUid();
Timer timer = mPkgTimers.get(userId, packageName);
if (timer == null) {
timer = new Timer(uid, userId, packageName);
mPkgTimers.add(userId, packageName, timer);
}
- if (mActivityManagerInternal.getUidProcessState(uid) == ActivityManager.PROCESS_STATE_TOP) {
- mTopStartedJobs.add(jobStatus);
- }
timer.startTrackingJob(jobStatus);
}
@@ -548,6 +592,36 @@
mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
changed = true;
}
+ int newMaxCountPerAllowedPeriod = Math.max(10,
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME);
+ if (mMaxJobCountPerAllowedTime != newMaxCountPerAllowedPeriod) {
+ mMaxJobCountPerAllowedTime = newMaxCountPerAllowedPeriod;
+ changed = true;
+ }
+ int newActiveMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+ Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE));
+ if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) {
+ mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount;
+ changed = true;
+ }
+ int newWorkingMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+ Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING));
+ if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) {
+ mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount;
+ changed = true;
+ }
+ int newFrequentMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+ Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT));
+ if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) {
+ mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount;
+ changed = true;
+ }
+ int newRareMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+ Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE));
+ if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) {
+ mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount;
+ changed = true;
+ }
if (changed) {
// Update job bookkeeping out of band.
@@ -631,18 +705,39 @@
return isTopStartedJob(jobStatus)
|| isUidInForeground(jobStatus.getSourceUid())
|| isWithinQuotaLocked(
- jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
}
- private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
+ @VisibleForTesting
+ boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
final int standbyBucket) {
if (standbyBucket == NEVER_INDEX) return false;
// This check is needed in case the flag is toggled after a job has been registered.
if (!mShouldThrottle) return true;
// Quota constraint is not enforced while charging or when parole is on.
- return mChargeTracker.isCharging() || mInParole
- || getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) > 0;
+ if (mChargeTracker.isCharging() || mInParole) {
+ return true;
+ }
+
+ return getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) > 0
+ && isUnderJobCountQuotaLocked(userId, packageName, standbyBucket);
+ }
+
+ private boolean isUnderJobCountQuotaLocked(final int userId, @NonNull final String packageName,
+ final int standbyBucket) {
+ ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket, false);
+ return isUnderJobCountQuotaLocked(stats, standbyBucket);
+ }
+
+ private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats,
+ final int standbyBucket) {
+ final long now = sElapsedRealtimeClock.millis();
+ final boolean isUnderAllowedTimeQuota =
+ (stats.jobCountExpirationTimeElapsed <= now
+ || stats.jobCountInAllowedTime < mMaxJobCountPerAllowedTime);
+ return isUnderAllowedTimeQuota
+ && (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]);
}
@VisibleForTesting
@@ -679,6 +774,13 @@
@NonNull
ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName,
final int standbyBucket) {
+ return getExecutionStatsLocked(userId, packageName, standbyBucket, true);
+ }
+
+ @NonNull
+ private ExecutionStats getExecutionStatsLocked(final int userId,
+ @NonNull final String packageName, final int standbyBucket,
+ final boolean refreshStatsIfOld) {
if (standbyBucket == NEVER_INDEX) {
Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app.");
return new ExecutionStats();
@@ -693,14 +795,16 @@
stats = new ExecutionStats();
appStats[standbyBucket] = stats;
}
- final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
- Timer timer = mPkgTimers.get(userId, packageName);
- if ((timer != null && timer.isActive())
- || stats.invalidTimeElapsed <= sElapsedRealtimeClock.millis()
- || stats.windowSizeMs != bucketWindowSizeMs) {
- // The stats are no longer valid.
- stats.windowSizeMs = bucketWindowSizeMs;
- updateExecutionStatsLocked(userId, packageName, stats);
+ if (refreshStatsIfOld) {
+ final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
+ Timer timer = mPkgTimers.get(userId, packageName);
+ if ((timer != null && timer.isActive())
+ || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis()
+ || stats.windowSizeMs != bucketWindowSizeMs) {
+ // The stats are no longer valid.
+ stats.windowSizeMs = bucketWindowSizeMs;
+ updateExecutionStatsLocked(userId, packageName, stats);
+ }
}
return stats;
@@ -717,14 +821,14 @@
Timer timer = mPkgTimers.get(userId, packageName);
final long nowElapsed = sElapsedRealtimeClock.millis();
- stats.invalidTimeElapsed = nowElapsed + MAX_PERIOD_MS;
+ stats.expirationTimeElapsed = nowElapsed + MAX_PERIOD_MS;
if (timer != null && timer.isActive()) {
stats.executionTimeInWindowMs =
stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed);
stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount();
// If the timer is active, the value will be stale at the next method call, so
// invalidate now.
- stats.invalidTimeElapsed = nowElapsed;
+ stats.expirationTimeElapsed = nowElapsed;
if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
nowElapsed - mAllowedTimeIntoQuotaMs);
@@ -800,7 +904,7 @@
break;
}
}
- stats.invalidTimeElapsed = nowElapsed + emptyTimeMs;
+ stats.expirationTimeElapsed = nowElapsed + emptyTimeMs;
}
private void invalidateAllExecutionStatsLocked(final int userId,
@@ -811,13 +915,35 @@
for (int i = 0; i < appStats.length; ++i) {
ExecutionStats stats = appStats[i];
if (stats != null) {
- stats.invalidTimeElapsed = nowElapsed;
+ stats.expirationTimeElapsed = nowElapsed;
}
}
}
}
@VisibleForTesting
+ void incrementJobCount(final int userId, @NonNull final String packageName, int count) {
+ final long now = sElapsedRealtimeClock.millis();
+ ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
+ if (appStats == null) {
+ appStats = new ExecutionStats[mBucketPeriodsMs.length];
+ mExecutionStatsCache.add(userId, packageName, appStats);
+ }
+ for (int i = 0; i < appStats.length; ++i) {
+ ExecutionStats stats = appStats[i];
+ if (stats == null) {
+ stats = new ExecutionStats();
+ appStats[i] = stats;
+ }
+ if (stats.jobCountExpirationTimeElapsed <= now) {
+ stats.jobCountExpirationTimeElapsed = now + mAllowedTimePerPeriodMs;
+ stats.jobCountInAllowedTime = 0;
+ }
+ stats.jobCountInAllowedTime += count;
+ }
+ }
+
+ @VisibleForTesting
void saveTimingSession(final int userId, @NonNull final String packageName,
@NonNull final TimingSession session) {
synchronized (mLock) {
@@ -1023,9 +1149,12 @@
final String pkgString = string(userId, packageName);
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
+ final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
+
QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
- && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs) {
+ && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
+ && isUnderJobCountQuota) {
// Already in quota. Why was this method called?
if (DEBUG) {
Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
@@ -1042,18 +1171,22 @@
mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
return;
}
+
if (alarmListener == null) {
alarmListener = new QcAlarmListener(userId, packageName);
mInQuotaAlarmListeners.add(userId, packageName, alarmListener);
}
// The time this app will have quota again.
- long inQuotaTimeElapsed =
- stats.quotaCutoffTimeElapsed + stats.windowSizeMs;
+ long inQuotaTimeElapsed = stats.quotaCutoffTimeElapsed + stats.windowSizeMs;
if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeMs) {
inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
stats.quotaCutoffTimeElapsed + MAX_PERIOD_MS);
}
+ if (!isUnderJobCountQuota) {
+ inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
+ stats.jobCountExpirationTimeElapsed + mAllowedTimePerPeriodMs);
+ }
// Only schedule the alarm if:
// 1. There isn't one currently scheduled
// 2. The new alarm is significantly earlier than the previous alarm (which could be the
@@ -1228,6 +1361,7 @@
mRunningBgJobs.add(jobStatus);
if (shouldTrackLocked()) {
mBgJobCount++;
+ incrementJobCount(mPkg.userId, mPkg.packageName, 1);
if (mRunningBgJobs.size() == 1) {
// Started tracking the first job.
mStartTimeElapsed = sElapsedRealtimeClock.millis();
@@ -1324,6 +1458,7 @@
// repeatedly plugged in and unplugged, or an app changes foreground state
// very frequently, the job count for a package may be artificially high.
mBgJobCount = mRunningBgJobs.size();
+ incrementJobCount(mPkg.userId, mPkg.packageName, mBgJobCount);
// Starting the timer means that all cached execution stats are now
// incorrect.
invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
@@ -1604,6 +1739,12 @@
@VisibleForTesting
@NonNull
+ int[] getBucketMaxJobCounts() {
+ return mMaxBucketJobCounts;
+ }
+
+ @VisibleForTesting
+ @NonNull
long[] getBucketWindowSizes() {
return mBucketPeriodsMs;
}
@@ -1631,6 +1772,11 @@
}
@VisibleForTesting
+ int getMaxJobCountPerAllowedTime() {
+ return mMaxJobCountPerAllowedTime;
+ }
+
+ @VisibleForTesting
@Nullable
List<TimingSession> getTimingSessions(int userId, String packageName) {
return mTimingSessions.get(userId, packageName);
diff --git a/services/core/java/com/android/server/location/ActivityRecognitionProxy.java b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
new file mode 100644
index 0000000..22fabb2
--- /dev/null
+++ b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 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.location;
+
+import android.content.Context;
+import android.hardware.location.ActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareClient;
+import android.hardware.location.IActivityRecognitionHardwareWatcher;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.server.ServiceWatcher;
+
+/**
+ * Proxy class to bind GmsCore to the ActivityRecognitionHardware.
+ *
+ * @hide
+ */
+public class ActivityRecognitionProxy {
+
+ private static final String TAG = "ActivityRecognitionProxy";
+
+ /**
+ * Creates an instance of the proxy and binds it to the appropriate FusedProvider.
+ *
+ * @return An instance of the proxy if it could be bound, null otherwise.
+ */
+ public static ActivityRecognitionProxy createAndBind(
+ Context context,
+ boolean activityRecognitionHardwareIsSupported,
+ ActivityRecognitionHardware activityRecognitionHardware,
+ int overlaySwitchResId,
+ int defaultServicePackageNameResId,
+ int initialPackageNameResId) {
+ ActivityRecognitionProxy activityRecognitionProxy = new ActivityRecognitionProxy(
+ context,
+ activityRecognitionHardwareIsSupported,
+ activityRecognitionHardware,
+ overlaySwitchResId,
+ defaultServicePackageNameResId,
+ initialPackageNameResId);
+
+ if (activityRecognitionProxy.mServiceWatcher.start()) {
+ return activityRecognitionProxy;
+ } else {
+ return null;
+ }
+ }
+
+ private final ServiceWatcher mServiceWatcher;
+ private final boolean mIsSupported;
+ private final ActivityRecognitionHardware mInstance;
+
+ private ActivityRecognitionProxy(
+ Context context,
+ boolean activityRecognitionHardwareIsSupported,
+ ActivityRecognitionHardware activityRecognitionHardware,
+ int overlaySwitchResId,
+ int defaultServicePackageNameResId,
+ int initialPackageNameResId) {
+ mIsSupported = activityRecognitionHardwareIsSupported;
+ mInstance = activityRecognitionHardware;
+
+ mServiceWatcher = new ServiceWatcher(
+ context,
+ TAG,
+ "com.android.location.service.ActivityRecognitionProvider",
+ overlaySwitchResId,
+ defaultServicePackageNameResId,
+ initialPackageNameResId,
+ BackgroundThread.getHandler()) {
+ @Override
+ protected void onBind() {
+ runOnBinder(ActivityRecognitionProxy.this::initializeService);
+ }
+ };
+ }
+
+ private void initializeService(IBinder binder) {
+ try {
+ String descriptor = binder.getInterfaceDescriptor();
+
+ if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(
+ descriptor)) {
+ IActivityRecognitionHardwareWatcher watcher =
+ IActivityRecognitionHardwareWatcher.Stub.asInterface(binder);
+ if (mInstance != null) {
+ watcher.onInstanceChanged(mInstance);
+ }
+ } else if (IActivityRecognitionHardwareClient.class.getCanonicalName()
+ .equals(descriptor)) {
+ IActivityRecognitionHardwareClient client =
+ IActivityRecognitionHardwareClient.Stub.asInterface(binder);
+ client.onAvailabilityChanged(mIsSupported, mInstance);
+ } else {
+ Log.e(TAG, "Invalid descriptor found on connection: " + descriptor);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 8734ceb6..a9ae74f 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -947,6 +947,10 @@
public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
String managedUserPassword) {
checkWritePermission(userId);
+ if (!mLockPatternUtils.hasSecureLockScreen()) {
+ throw new UnsupportedOperationException(
+ "This operation requires secure lock screen feature.");
+ }
synchronized (mSeparateChallengeLock) {
setSeparateProfileChallengeEnabledLocked(userId, enabled, managedUserPassword);
}
@@ -1305,6 +1309,10 @@
public void setLockCredential(String credential, int type, String savedCredential,
int requestedQuality, int userId)
throws RemoteException {
+ if (!mLockPatternUtils.hasSecureLockScreen()) {
+ throw new UnsupportedOperationException(
+ "This operation requires secure lock screen feature");
+ }
checkWritePermission(userId);
synchronized (mSeparateChallengeLock) {
setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId);
@@ -2906,6 +2914,10 @@
@Override
public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle,
byte[] token, int requestedQuality, int userId) {
+ if (!mLockPatternUtils.hasSecureLockScreen()) {
+ throw new UnsupportedOperationException(
+ "This operation requires secure lock screen feature.");
+ }
try {
return LockSettingsService.this.setLockCredentialWithToken(credential, type,
tokenHandle, token, requestedQuality, userId);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 07f23ce..6163077 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -18,6 +18,7 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+
import static com.android.internal.widget.LockPatternUtils.stringToPattern;
import android.app.ActivityManager;
@@ -58,6 +59,18 @@
mCurrentUserId = ActivityManager.getService().getCurrentUser().id;
parseArgs();
+ if (!mLockPatternUtils.hasSecureLockScreen()) {
+ switch (cmd) {
+ case COMMAND_HELP:
+ case COMMAND_GET_DISABLED:
+ case COMMAND_SET_DISABLED:
+ break;
+ default:
+ getErrPrintWriter().println(
+ "The device does not support lock screen - ignoring the command.");
+ return -1;
+ }
+ }
if (!checkCredential()) {
return -1;
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index d8c2432..af790f2 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -78,6 +78,7 @@
private final String mPackageName;
private final String mTag;
private final ControllerLink mController;
+ private final MediaSession.Token mSessionToken;
private final SessionLink mSession;
private final SessionCb mSessionCb;
private final MediaSessionService.ServiceImpl mService;
@@ -128,6 +129,7 @@
mPackageName = ownerPackageName;
mTag = tag;
mController = new ControllerLink(new ControllerStub());
+ mSessionToken = new MediaSession.Token(mController);
mSession = new SessionLink(new SessionStub());
mSessionCb = new SessionCb(cb);
mService = service;
@@ -157,6 +159,15 @@
}
/**
+ * Get the session token for creating {@link MediaController}.
+ *
+ * @return The session token.
+ */
+ public MediaSession.Token getSessionToken() {
+ return mSessionToken;
+ }
+
+ /**
* Get the info for this session.
*
* @return Info that identifies this session.
diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
index e3ae8a7..1541b1d 100644
--- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
@@ -51,7 +51,6 @@
import android.media.session.IOnMediaKeyListener;
import android.media.session.IOnVolumeKeyLongPressListener;
import android.media.session.ISession2TokensListener;
-import android.media.session.ISessionController;
import android.media.session.ISessionManager;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
@@ -290,9 +289,7 @@
return;
}
try {
- mRvc.remoteVolumeChanged(
- ISessionController.Stub.asInterface(session.getControllerLink().getBinder()),
- flags);
+ mRvc.remoteVolumeChanged(session.getSessionToken(), flags);
} catch (Exception e) {
Log.wtf(TAG, "Error sending volume change to system UI.", e);
}
@@ -618,7 +615,7 @@
int size = records.size();
ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
for (int i = 0; i < size; i++) {
- tokens.add(new MediaSession.Token(records.get(i).getControllerLink()));
+ tokens.add(records.get(i).getSessionToken());
}
pushRemoteVolumeUpdateLocked(userId);
for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
@@ -645,9 +642,7 @@
return;
}
MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
- mRvc.updateRemoteController(record == null ? null
- : ISessionController.Stub.asInterface(
- record.getControllerLink().getBinder()));
+ mRvc.updateRemoteController(record == null ? null : record.getSessionToken());
} catch (RemoteException e) {
Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
}
@@ -864,7 +859,7 @@
MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
if (mediaButtonSession != null) {
mCallback.onAddressedPlayerChangedToMediaSession(
- new MediaSession.Token(mediaButtonSession.getControllerLink()));
+ mediaButtonSession.getSessionToken());
} else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
mCurrentFullUserRecord.mLastMediaButtonReceiver
@@ -1804,7 +1799,7 @@
if (mCurrentFullUserRecord.mCallback != null) {
try {
mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
- keyEvent, new MediaSession.Token(session.getControllerLink()));
+ keyEvent, session.getSessionToken());
} catch (RemoteException e) {
Log.w(TAG, "Failed to send callback", e);
}
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index ee60daa..c2dc554 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -42,7 +42,8 @@
void onNotificationVisibilityChanged(
NotificationVisibility[] newlyVisibleKeys,
NotificationVisibility[] noLongerVisibleKeys);
- void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded);
+ void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded,
+ int notificationLocation);
void onNotificationDirectReplied(String key);
void onNotificationSettingsViewed(String key);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 20c4da4..a164686 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -845,10 +845,12 @@
reportSeen(r);
}
r.setVisibility(true, nv.rank, nv.count);
+ boolean isHun = (nv.location
+ == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
// hasBeenVisiblyExpanded must be called after updating the expansion state of
// the NotificationRecord to ensure the expansion state is up-to-date.
- if (r.hasBeenVisiblyExpanded()) {
- logSmartSuggestionsVisible(r);
+ if (isHun || r.hasBeenVisiblyExpanded()) {
+ logSmartSuggestionsVisible(r, nv.location.toMetricsEventEnum());
}
maybeRecordInterruptionLocked(r);
nv.recycle();
@@ -868,7 +870,7 @@
@Override
public void onNotificationExpansionChanged(String key,
- boolean userAction, boolean expanded) {
+ boolean userAction, boolean expanded, int notificationLocation) {
synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r != null) {
@@ -876,7 +878,7 @@
// hasBeenVisiblyExpanded must be called after updating the expansion state of
// the NotificationRecord to ensure the expansion state is up-to-date.
if (r.hasBeenVisiblyExpanded()) {
- logSmartSuggestionsVisible(r);
+ logSmartSuggestionsVisible(r, notificationLocation);
}
if (userAction) {
MetricsLogger.action(r.getItemLogMaker()
@@ -952,7 +954,7 @@
};
@VisibleForTesting
- void logSmartSuggestionsVisible(NotificationRecord r) {
+ void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) {
// If the newly visible notification has smart suggestions
// then log that the user has seen them.
if ((r.getNumSmartRepliesAdded() > 0 || r.getNumSmartActionsAdded() > 0)
@@ -966,7 +968,10 @@
r.getNumSmartActionsAdded())
.addTaggedData(
MetricsEvent.NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED,
- r.getSuggestionsGeneratedByAssistant() ? 1 : 0);
+ r.getSuggestionsGeneratedByAssistant() ? 1 : 0)
+ // The fields in the NotificationVisibility.NotificationLocation enum map
+ // directly to the fields in the MetricsEvent.NotificationLocation enum.
+ .addTaggedData(MetricsEvent.NOTIFICATION_LOCATION, notificationLocation);
mMetricsLogger.write(logMaker);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 5598741..02fc51f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -1263,7 +1263,7 @@
public LogMaker getAdjustmentLogMaker() {
return getLogMaker()
.setCategory(MetricsEvent.NOTIFICATION_ITEM)
- .setType(MetricsEvent.NOTIFICATION_ASSISTANT_ADJUSTMENT);
+ .setType(MetricsEvent.TYPE_NOTIFICATION_ASSISTANT_ADJUSTMENT);
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/os/BugreportManagerService.java b/services/core/java/com/android/server/os/BugreportManagerService.java
index e241591..bee7a8b 100644
--- a/services/core/java/com/android/server/os/BugreportManagerService.java
+++ b/services/core/java/com/android/server/os/BugreportManagerService.java
@@ -37,7 +37,6 @@
@Override
public void onStart() {
mService = new BugreportManagerServiceImpl(getContext());
- // TODO(b/111441001): Needs sepolicy to be submitted first.
- // publishBinderService(Context.BUGREPORT_SERVICE, mService);
+ publishBinderService(Context.BUGREPORT_SERVICE, mService);
}
}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 1178cc1..f736056 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -44,6 +44,7 @@
*/
class BugreportManagerServiceImpl extends IDumpstate.Stub {
private static final String TAG = "BugreportManagerService";
+ private static final String BUGREPORT_SERVICE = "bugreportd";
private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
private IDumpstate mDs = null;
@@ -64,6 +65,8 @@
throw new UnsupportedOperationException("setListener is not allowed on this service");
}
+ // TODO(b/111441001): Intercept onFinished here in system server and shutdown
+ // the bugreportd service.
@Override
@RequiresPermission(android.Manifest.permission.DUMP)
public void startBugreport(int callingUidUnused, String callingPackage,
@@ -84,6 +87,14 @@
bugreportFd, screenshotFd, bugreportMode, listener);
}
+ @Override
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public void cancelBugreport() throws RemoteException {
+ // This tells init to cancel bugreportd service.
+ SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
+ mDs = null;
+ }
+
private boolean validate(@BugreportParams.BugreportMode int mode) {
if (mode != BugreportParams.BUGREPORT_MODE_FULL
&& mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
@@ -107,7 +118,7 @@
*/
private IDumpstate getDumpstateService() {
// Start bugreport service.
- SystemProperties.set("ctl.start", "bugreport");
+ SystemProperties.set("ctl.start", BUGREPORT_SERVICE);
IDumpstate ds = null;
boolean timedOut = false;
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 65fc982..ad9ac12 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -50,6 +50,7 @@
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
/**
* {@hide}
@@ -57,7 +58,7 @@
public class BackgroundDexOptService extends JobService {
private static final String TAG = "BackgroundDexOptService";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int JOB_IDLE_OPTIMIZE = 800;
private static final int JOB_POST_BOOT_UPDATE = 801;
@@ -102,7 +103,6 @@
private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
private final File mDataDir = Environment.getDataDirectory();
-
private static final long mDowngradeUnusedAppsThresholdInMillis =
getDowngradeUnusedAppsThresholdInMillis();
@@ -275,21 +275,18 @@
long lowStorageThreshold = getLowStorageThreshold(context);
// Optimize primary apks.
- int result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ true,
- sFailedPackageNamesPrimary);
-
+ int result = optimizePackages(pm, pkgs, lowStorageThreshold,
+ /*isForPrimaryDex=*/ true);
if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
return result;
}
-
- if (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
+ if (supportSecondaryDex()) {
result = reconcileSecondaryDexFiles(pm.getDexManager());
if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
return result;
}
-
- result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false,
- sFailedPackageNamesSecondary);
+ result = optimizePackages(pm, pkgs, lowStorageThreshold,
+ /*isForPrimaryDex=*/ false);
}
return result;
}
@@ -339,94 +336,86 @@
}
private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
- long lowStorageThreshold, boolean is_for_primary_dex,
- ArraySet<String> failedPackageNames) {
+ long lowStorageThreshold, boolean isForPrimaryDex) {
ArraySet<String> updatedPackages = new ArraySet<>();
Set<String> unusedPackages = pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
+ Log.d(TAG, "Unsused Packages " + String.join(",", unusedPackages));
// Only downgrade apps when space is low on device.
// Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
// up disk before user hits the actual lowStorageThreshold.
final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE *
lowStorageThreshold;
boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
+ Log.d(TAG, "Should Downgrade " + shouldDowngrade);
+ boolean dex_opt_performed = false;
for (String pkg : pkgs) {
int abort_code = abortIdleOptimizations(lowStorageThreshold);
if (abort_code == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
return abort_code;
}
-
- synchronized (failedPackageNames) {
- if (failedPackageNames.contains(pkg)) {
- // Skip previously failing package
- continue;
- }
- }
-
- int reason;
- boolean downgrade;
- long package_size_before = 0; //used when the app is downgraded
// Downgrade unused packages.
if (unusedPackages.contains(pkg) && shouldDowngrade) {
- package_size_before = getPackageSize(pm, pkg);
- // This applies for system apps or if packages location is not a directory, i.e.
- // monolithic install.
- if (is_for_primary_dex && !pm.canHaveOatDir(pkg)) {
- // For apps that don't have the oat directory, instead of downgrading,
- // remove their compiler artifacts from dalvik cache.
- pm.deleteOatArtifactsOfPackage(pkg);
+ dex_opt_performed = downgradePackage(pm, pkg, isForPrimaryDex);
+ } else {
+ if (abort_code == OPTIMIZE_ABORT_NO_SPACE_LEFT) {
+ // can't dexopt because of low space.
continue;
- } else {
- reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
- downgrade = true;
}
- } else if (abort_code != OPTIMIZE_ABORT_NO_SPACE_LEFT) {
- reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
- downgrade = false;
- } else {
- // can't dexopt because of low space.
- continue;
+ dex_opt_performed = optimizePackage(pm, pkg, isForPrimaryDex);
}
-
- synchronized (failedPackageNames) {
- // Conservatively add package to the list of failing ones in case
- // performDexOpt never returns.
- failedPackageNames.add(pkg);
- }
-
- // Optimize package if needed. Note that there can be no race between
- // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
- boolean success;
- int dexoptFlags =
- DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
- DexoptOptions.DEXOPT_BOOT_COMPLETE |
- (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0) |
- DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
- if (is_for_primary_dex) {
- int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, reason,
- dexoptFlags));
- success = result != PackageDexOptimizer.DEX_OPT_FAILED;
- if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
- updatedPackages.add(pkg);
- }
- } else {
- success = pm.performDexOpt(new DexoptOptions(pkg,
- reason, dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX));
- }
- if (success) {
- // Dexopt succeeded, remove package from the list of failing ones.
- synchronized (failedPackageNames) {
- failedPackageNames.remove(pkg);
- }
- if (downgrade) {
- StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before,
- getPackageSize(pm, pkg), /*aggressive=*/ false);
- }
+ if (dex_opt_performed) {
+ updatedPackages.add(pkg);
}
}
+
notifyPinService(updatedPackages);
return OPTIMIZE_PROCESSED;
}
+
+ /**
+ * Try to downgrade the package to a smaller compilation filter.
+ * eg. if the package is in speed-profile the package will be downgraded to verify.
+ * @param pm PackageManagerService
+ * @param pkg The package to be downgraded.
+ * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
+ * @return true if the package was downgraded.
+ */
+ private boolean downgradePackage(PackageManagerService pm, String pkg,
+ boolean isForPrimaryDex) {
+ Log.d(TAG, "Downgrading " + pkg);
+ boolean dex_opt_performed = false;
+ int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
+ int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
+ | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB
+ | DexoptOptions.DEXOPT_DOWNGRADE;
+ long package_size_before = getPackageSize(pm, pkg);
+
+ if (isForPrimaryDex) {
+ // This applies for system apps or if packages location is not a directory, i.e.
+ // monolithic install.
+ if (!pm.canHaveOatDir(pkg)) {
+ // For apps that don't have the oat directory, instead of downgrading,
+ // remove their compiler artifacts from dalvik cache.
+ pm.deleteOatArtifactsOfPackage(pkg);
+ } else {
+ dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags);
+ }
+ } else {
+ dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+ }
+
+ if (dex_opt_performed) {
+ StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before,
+ getPackageSize(pm, pkg), /*aggressive=*/ false);
+ }
+ return dex_opt_performed;
+ }
+
+ private boolean supportSecondaryDex() {
+ return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
+ }
+
private int reconcileSecondaryDexFiles(DexManager dm) {
// TODO(calin): should we blacklist packages for which we fail to reconcile?
for (String p : dm.getAllPackagesWithSecondaryDexFiles()) {
@@ -438,6 +427,73 @@
return OPTIMIZE_PROCESSED;
}
+ /**
+ *
+ * Optimize package if needed. Note that there can be no race between
+ * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
+ * @param pm An instance of PackageManagerService
+ * @param pkg The package to be downgraded.
+ * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
+ * @return true if the package was downgraded.
+ */
+ private boolean optimizePackage(PackageManagerService pm, String pkg,
+ boolean isForPrimaryDex) {
+ int reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
+ int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
+ | DexoptOptions.DEXOPT_BOOT_COMPLETE
+ | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
+
+ return isForPrimaryDex
+ ? performDexOptPrimary(pm, pkg, reason, dexoptFlags)
+ : performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+ }
+
+ private boolean performDexOptPrimary(PackageManagerService pm, String pkg, int reason,
+ int dexoptFlags) {
+ int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false,
+ () -> pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags)));
+ return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
+ }
+
+ private boolean performDexOptSecondary(PackageManagerService pm, String pkg, int reason,
+ int dexoptFlags) {
+ DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason,
+ dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
+ int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true,
+ () -> pm.performDexOpt(dexoptOptions)
+ ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED
+ );
+ return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
+ }
+
+ /**
+ * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails
+ * the package is added to the list of failed packages.
+ * Return one of following result:
+ * {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
+ * {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
+ * {@link PackageDexOptimizer#DEX_OPT_FAILED}
+ */
+ private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex,
+ Supplier<Integer> performDexOptWrapper) {
+ ArraySet<String> sFailedPackageNames =
+ isForPrimaryDex ? sFailedPackageNamesPrimary : sFailedPackageNamesSecondary;
+ synchronized (sFailedPackageNames) {
+ if (sFailedPackageNames.contains(pkg)) {
+ // Skip previously failing package
+ return PackageDexOptimizer.DEX_OPT_SKIPPED;
+ }
+ sFailedPackageNames.add(pkg);
+ }
+ int result = performDexOptWrapper.get();
+ if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
+ synchronized (sFailedPackageNames) {
+ sFailedPackageNames.remove(pkg);
+ }
+ }
+ return result;
+ }
+
// Evaluate whether or not idle optimizations should continue.
private int abortIdleOptimizations(long lowStorageThreshold) {
if (mAbortIdleOptimization.get()) {
diff --git a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
index 2ae424d..5b765df 100644
--- a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
+++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
@@ -22,63 +22,117 @@
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
+import android.os.Process;
import android.os.ServiceManager;
+import android.util.ByteStringUtils;
+import android.util.EventLog;
import android.util.Log;
import com.android.server.pm.dex.DexLogger;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
- * Scheduled job to trigger logging of app dynamic code loading. This runs daily while idle and
- * charging. The actual logging is performed by {@link DexLogger}.
+ * Scheduled jobs related to logging of app dynamic code loading. The idle logging job runs daily
+ * while idle and charging and calls {@link DexLogger} to write dynamic code information to the
+ * event log. The audit watching job scans the event log periodically while idle to find AVC audit
+ * messages indicating use of dynamic native code and adds the information to {@link DexLogger}.
* {@hide}
*/
public class DynamicCodeLoggingService extends JobService {
private static final String TAG = DynamicCodeLoggingService.class.getName();
- private static final int JOB_ID = 2030028;
- private static final long PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1);
-
- private volatile boolean mStopRequested = false;
-
private static final boolean DEBUG = false;
+ private static final int IDLE_LOGGING_JOB_ID = 2030028;
+ private static final int AUDIT_WATCHING_JOB_ID = 203142925;
+
+ private static final long IDLE_LOGGING_PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1);
+ private static final long AUDIT_WATCHING_PERIOD_MILLIS = TimeUnit.HOURS.toMillis(2);
+
+ private static final int AUDIT_AVC = 1400; // Defined in linux/audit.h
+ private static final String AVC_PREFIX = "type=" + AUDIT_AVC + " ";
+
+ private static final Pattern EXECUTE_NATIVE_AUDIT_PATTERN =
+ Pattern.compile(".*\\bavc: granted \\{ execute(?:_no_trans|) \\} .*"
+ + "\\bpath=(?:\"([^\" ]*)\"|([0-9A-F]+)) .*"
+ + "\\bscontext=u:r:untrusted_app_2(?:5|7):.*"
+ + "\\btcontext=u:object_r:app_data_file:.*"
+ + "\\btclass=file\\b.*");
+
+ private volatile boolean mIdleLoggingStopRequested = false;
+ private volatile boolean mAuditWatchingStopRequested = false;
+
/**
- * Schedule our job with the {@link JobScheduler}.
+ * Schedule our jobs with the {@link JobScheduler}.
*/
public static void schedule(Context context) {
ComponentName serviceName = new ComponentName(
"android", DynamicCodeLoggingService.class.getName());
JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
- js.schedule(new JobInfo.Builder(JOB_ID, serviceName)
+ js.schedule(new JobInfo.Builder(IDLE_LOGGING_JOB_ID, serviceName)
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
- .setPeriodic(PERIOD_MILLIS)
+ .setPeriodic(IDLE_LOGGING_PERIOD_MILLIS)
.build());
+ js.schedule(new JobInfo.Builder(AUDIT_WATCHING_JOB_ID, serviceName)
+ .setRequiresDeviceIdle(true)
+ .setRequiresBatteryNotLow(true)
+ .setPeriodic(AUDIT_WATCHING_PERIOD_MILLIS)
+ .build());
+
if (DEBUG) {
- Log.d(TAG, "Job scheduled");
+ Log.d(TAG, "Jobs scheduled");
}
}
@Override
public boolean onStartJob(JobParameters params) {
+ int jobId = params.getJobId();
if (DEBUG) {
- Log.d(TAG, "onStartJob");
+ Log.d(TAG, "onStartJob " + jobId);
}
- mStopRequested = false;
- new IdleLoggingThread(params).start();
- return true; // Job is running on another thread
+ switch (jobId) {
+ case IDLE_LOGGING_JOB_ID:
+ mIdleLoggingStopRequested = false;
+ new IdleLoggingThread(params).start();
+ return true; // Job is running on another thread
+ case AUDIT_WATCHING_JOB_ID:
+ mAuditWatchingStopRequested = false;
+ new AuditWatchingThread(params).start();
+ return true; // Job is running on another thread
+ default:
+ // Shouldn't happen, but indicate nothing is running.
+ return false;
+ }
}
@Override
public boolean onStopJob(JobParameters params) {
+ int jobId = params.getJobId();
if (DEBUG) {
- Log.d(TAG, "onStopJob");
+ Log.d(TAG, "onStopJob " + jobId);
}
- mStopRequested = true;
- return true; // Requests job be re-scheduled.
+ switch (jobId) {
+ case IDLE_LOGGING_JOB_ID:
+ mIdleLoggingStopRequested = true;
+ return true; // Requests job be re-scheduled.
+ case AUDIT_WATCHING_JOB_ID:
+ mAuditWatchingStopRequested = true;
+ return true; // Requests job be re-scheduled.
+ default:
+ return false;
+ }
+ }
+
+ private static DexLogger getDexLogger() {
+ PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package");
+ return pm.getDexManager().getDexLogger();
}
private class IdleLoggingThread extends Thread {
@@ -92,14 +146,13 @@
@Override
public void run() {
if (DEBUG) {
- Log.d(TAG, "Starting logging run");
+ Log.d(TAG, "Starting IdleLoggingJob run");
}
- PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package");
- DexLogger dexLogger = pm.getDexManager().getDexLogger();
+ DexLogger dexLogger = getDexLogger();
for (String packageName : dexLogger.getAllPackagesWithDynamicCodeLoading()) {
- if (mStopRequested) {
- Log.w(TAG, "Stopping logging run at scheduler request");
+ if (mIdleLoggingStopRequested) {
+ Log.w(TAG, "Stopping IdleLoggingJob run at scheduler request");
return;
}
@@ -108,8 +161,128 @@
jobFinished(mParams, /* reschedule */ false);
if (DEBUG) {
- Log.d(TAG, "Finished logging run");
+ Log.d(TAG, "Finished IdleLoggingJob run");
}
}
}
+
+ private class AuditWatchingThread extends Thread {
+ private final JobParameters mParams;
+
+ AuditWatchingThread(JobParameters params) {
+ super("DynamicCodeLoggingService_AuditWatchingJob");
+ mParams = params;
+ }
+
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "Starting AuditWatchingJob run");
+ }
+
+ if (processAuditEvents()) {
+ jobFinished(mParams, /* reschedule */ false);
+ if (DEBUG) {
+ Log.d(TAG, "Finished AuditWatchingJob run");
+ }
+ }
+ }
+
+ private boolean processAuditEvents() {
+ // Scan the event log for SELinux (avc) audit messages indicating when an
+ // (untrusted) app has executed native code from an app data
+ // file. Matches are recorded in DexLogger.
+ //
+ // These messages come from the kernel audit system via logd. (Note that
+ // some devices may not generate these messages at all, or the format may
+ // be different, in which case nothing will be recorded.)
+ //
+ // The messages use the auditd tag and the uid of the app that executed
+ // the code.
+ //
+ // A typical message might look like this:
+ // type=1400 audit(0.0:521): avc: granted { execute } for comm="executable"
+ // path="/data/data/com.dummy.app/executable" dev="sda13" ino=1655302
+ // scontext=u:r:untrusted_app_27:s0:c66,c257,c512,c768
+ // tcontext=u:object_r:app_data_file:s0:c66,c257,c512,c768 tclass=file
+ //
+ // The information we want is the uid and the path. (Note this may be
+ // either a quoted string, as shown above, or a sequence of hex-encoded
+ // bytes.)
+ //
+ // On each run we process all the matching events in the log. This may
+ // mean re-processing events we have already seen, and in any case there
+ // may be duplicate events for the same app+file. These are de-duplicated
+ // by DexLogger.
+ //
+ // Note that any app can write a message to the event log, including one
+ // that looks exactly like an AVC audit message, so the information may
+ // be spoofed by an app; in such a case the uid we see will be the app
+ // that generated the spoof message.
+
+ try {
+ int[] tags = { EventLog.getTagCode("auditd") };
+ if (tags[0] == -1) {
+ // auditd is not a registered tag on this system, so there can't be any messages
+ // of interest.
+ return true;
+ }
+
+ DexLogger dexLogger = getDexLogger();
+
+ List<EventLog.Event> events = new ArrayList<>();
+ EventLog.readEvents(tags, events);
+
+ for (int i = 0; i < events.size(); ++i) {
+ if (mAuditWatchingStopRequested) {
+ Log.w(TAG, "Stopping AuditWatchingJob run at scheduler request");
+ return false;
+ }
+
+ EventLog.Event event = events.get(i);
+
+ // Discard clearly unrelated messages as quickly as we can.
+ int uid = event.getUid();
+ if (!Process.isApplicationUid(uid)) {
+ continue;
+ }
+ Object data = event.getData();
+ if (!(data instanceof String)) {
+ continue;
+ }
+ String message = (String) data;
+ if (!message.startsWith(AVC_PREFIX)) {
+ continue;
+ }
+
+ // And then use a regular expression to verify it's one of the messages we're
+ // interested in and to extract the path of the file being loaded.
+ Matcher matcher = EXECUTE_NATIVE_AUDIT_PATTERN.matcher(message);
+ if (!matcher.matches()) {
+ continue;
+ }
+ String path = matcher.group(1);
+ if (path == null) {
+ // If the path contains spaces or various weird characters the kernel
+ // hex-encodes the bytes; we need to undo that.
+ path = unhex(matcher.group(2));
+ }
+ dexLogger.recordNative(uid, path);
+ }
+
+ return true;
+ } catch (Exception e) {
+ Log.e(TAG, "AuditWatchingJob failed", e);
+ return true;
+ }
+ }
+ }
+
+ private static String unhex(String hexEncodedPath) {
+ byte[] bytes = ByteStringUtils.fromHexToByteArray(hexEncodedPath);
+ if (bytes == null || bytes.length == 0) {
+ return "";
+ }
+ return new String(bytes);
+ }
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 8a6105c..efafdfa 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -611,6 +611,31 @@
}
}
+ public boolean snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags)
+ throws InstallerException {
+ if (!checkBeforeRemote()) return false;
+
+ try {
+ mInstalld.snapshotAppData(null, pkg, userId, storageFlags);
+ return true;
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
+ public boolean restoreAppDataSnapshot(String pkg, @AppIdInt int appId, long ceDataInode,
+ String seInfo, @UserIdInt int userId, int storageFlags) throws InstallerException {
+ if (!checkBeforeRemote()) return false;
+
+ try {
+ mInstalld.restoreAppDataSnapshot(null, pkg, appId, ceDataInode, seInfo, userId,
+ storageFlags);
+ return true;
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
private static void assertValidInstructionSet(String instructionSet)
throws InstallerException {
for (String abi : Build.SUPPORTED_ABIS) {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index a33f14b..d0ef4f1 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -25,6 +25,7 @@
import android.app.IApplicationThread;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
+import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -135,6 +136,7 @@
private final Context mContext;
private final UserManager mUm;
private final UserManagerInternal mUserManagerInternal;
+ private final UsageStatsManagerInternal mUsageStatsManagerInternal;
private final ActivityManagerInternal mActivityManagerInternal;
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
private final ShortcutServiceInternal mShortcutServiceInternal;
@@ -156,6 +158,8 @@
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mUserManagerInternal = Preconditions.checkNotNull(
LocalServices.getService(UserManagerInternal.class));
+ mUsageStatsManagerInternal = Preconditions.checkNotNull(
+ LocalServices.getService(UsageStatsManagerInternal.class));
mActivityManagerInternal = Preconditions.checkNotNull(
LocalServices.getService(ActivityManagerInternal.class));
mActivityTaskManagerInternal = Preconditions.checkNotNull(
@@ -671,6 +675,30 @@
}
}
+ @Override
+ public LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage,
+ String packageName, UserHandle user) {
+ verifyCallingPackage(callingPackage);
+ if (!canAccessProfile(user.getIdentifier(), "Cannot access usage limit")) {
+ return null;
+ }
+
+ final PackageManagerInternal pmi =
+ LocalServices.getService(PackageManagerInternal.class);
+ final ComponentName cn = pmi.getDefaultHomeActivity(user.getIdentifier());
+ if (!cn.getPackageName().equals(callingPackage)) {
+ throw new SecurityException("Caller is not the active launcher");
+ }
+
+ final UsageStatsManagerInternal.AppUsageLimitData data =
+ mUsageStatsManagerInternal.getAppUsageLimit(packageName, user);
+ if (data == null) {
+ return null;
+ }
+ return new LauncherApps.AppUsageLimit(
+ data.isGroupLimit(), data.getTotalUsageLimit(), data.getUsageRemaining());
+ }
+
private void ensureShortcutPermission(@NonNull String callingPackage) {
verifyCallingPackage(callingPackage);
if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
new file mode 100644
index 0000000..60d7925
--- /dev/null
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -0,0 +1,76 @@
+hackbod@android.com
+hackbod@google.com
+jsharkey@android.com
+jsharkey@google.com
+narayan@google.com
+patb@google.com
+svetoslavganov@android.com
+svetoslavganov@google.com
+toddke@android.com
+toddke@google.com
+
+# dex
+per-file AbstractStatsBase.java = agampe@google.com
+per-file AbstractStatsBase.java = calin@google.com
+per-file AbstractStatsBase.java = ngeoffray@google.com
+per-file BackgroundDexOptService.java = agampe@google.com
+per-file BackgroundDexOptService.java = calin@google.com
+per-file BackgroundDexOptService.java = ngeoffray@google.com
+per-file CompilerStats.java = agampe@google.com
+per-file CompilerStats.java = calin@google.com
+per-file CompilerStats.java = ngeoffray@google.com
+per-file InstructionSets.java = agampe@google.com
+per-file InstructionSets.java = calin@google.com
+per-file InstructionSets.java = ngeoffray@google.com
+per-file OtaDexoptService.java = agampe@google.com
+per-file OtaDexoptService.java = calin@google.com
+per-file OtaDexoptService.java = ngeoffray@google.com
+per-file OtaDexoptShellCommand.java = agampe@google.com
+per-file OtaDexoptShellCommand.java = calin@google.com
+per-file OtaDexoptShellCommand.java = ngeoffray@google.com
+per-file PackageDexOptimizer.java = agampe@google.com
+per-file PackageDexOptimizer.java = calin@google.com
+per-file PackageDexOptimizer.java = ngeoffray@google.com
+per-file PackageManagerServiceCompilerMapping.java = agampe@google.com
+per-file PackageManagerServiceCompilerMapping.java = calin@google.com
+per-file PackageManagerServiceCompilerMapping.java = ngeoffray@google.com
+per-file PackageUsage.java = agampe@google.com
+per-file PackageUsage.java = calin@google.com
+per-file PackageUsage.java = ngeoffray@google.com
+
+# multi user / cross profile
+per-file CrossProfileAppsServiceImpl.java = omakoto@google.com
+per-file CrossProfileAppsServiceImpl.java = yamasani@google.com
+per-file CrossProfileAppsService.java = omakoto@google.com
+per-file CrossProfileAppsService.java = yamasani@google.com
+per-file CrossProfileIntentFilter.java = omakoto@google.com
+per-file CrossProfileIntentFilter.java = yamasani@google.com
+per-file CrossProfileIntentResolver.java = omakoto@google.com
+per-file CrossProfileIntentResolver.java = yamasani@google.com
+per-file UserManagerService.java = omakoto@google.com
+per-file UserManagerService.java = yamasani@google.com
+per-file UserRestrictionsUtils.java = omakoto@google.com
+per-file UserRestrictionsUtils.java = yamasani@google.com
+
+# security
+per-file KeySetHandle.java = cbrubaker@google.com
+per-file KeySetManagerService.java = cbrubaker@google.com
+per-file PackageKeySetData.java = cbrubaker@google.com
+per-file PackageSignatures.java = cbrubaker@google.com
+per-file SELinuxMMAC.java = cbrubaker@google.com
+
+# shortcuts
+per-file LauncherAppsService.java = omakoto@google.com
+per-file ShareTargetInfo.java = omakoto@google.com
+per-file ShortcutBitmapSaver.java = omakoto@google.com
+per-file ShortcutDumpFiles.java = omakoto@google.com
+per-file ShortcutLauncher.java = omakoto@google.com
+per-file ShortcutNonPersistentUser.java = omakoto@google.com
+per-file ShortcutPackage.java = omakoto@google.com
+per-file ShortcutPackageInfo.java = omakoto@google.com
+per-file ShortcutPackageItem.java = omakoto@google.com
+per-file ShortcutParser.java = omakoto@google.com
+per-file ShortcutRequestPinProcessor.java = omakoto@google.com
+per-file ShortcutService.java = omakoto@google.com
+per-file ShortcutUser.java = omakoto@google.com
+
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 5412e94..94b1b36 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -506,8 +506,10 @@
boolean isUsedByOtherApps) {
int flags = info.flags;
boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
- // When a priv app is configured to run out of box, only verify it.
- if (info.isPrivilegedApp() && DexManager.isPackageSelectedToRunOob(info.packageName)) {
+ // When an app or priv app is configured to run out of box, only verify it.
+ if (info.isCodeIntegrityPreferred()
+ || (info.isPrivilegedApp()
+ && DexManager.isPackageSelectedToRunOob(info.packageName))) {
return "verify";
}
if (vmSafeMode) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index bf4e272..0ab2a73 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -310,7 +310,6 @@
in.setInput(fis, StandardCharsets.UTF_8.name());
int type;
- PackageInstallerSession currentSession = null;
while ((type = in.next()) != END_DOCUMENT) {
if (type == START_TAG) {
final String tag = in.getName();
@@ -320,9 +319,7 @@
session = PackageInstallerSession.readFromXml(in, mInternalCallback,
mContext, mPm, mInstallThread.getLooper(), mStagingManager,
mSessionsDir, this);
- currentSession = session;
} catch (Exception e) {
- currentSession = null;
Slog.e(TAG, "Could not read session", e);
continue;
}
@@ -347,10 +344,6 @@
addHistoricalSessionLocked(session);
}
mAllocatedSessions.put(session.sessionId, true);
- } else if (currentSession != null
- && PackageInstallerSession.TAG_CHILD_SESSION.equals(tag)) {
- currentSession.addChildSessionIdInternal(
- PackageInstallerSession.readChildSessionIdFromXml(in));
}
}
}
@@ -1132,6 +1125,7 @@
public void onStagedSessionChanged(PackageInstallerSession session) {
writeSessionsAsync();
+ // TODO(b/118865310): don't send broadcast if system is not ready.
mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 12d335d..b8825bb 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -992,8 +992,12 @@
// Read transfers from the original owner stay open, but as the session's data
// cannot be modified anymore, there is no leak of information. For staged sessions,
- // further validation may be performed by the staging manager.
+ // further validation is performed by the staging manager.
if (!params.isMultiPackage) {
+ if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
+ // For APEX, validation is done by StagingManager post-commit.
+ return;
+ }
final PackageInfo pkgInfo = mPm.getPackageInfo(
params.appPackageName, PackageManager.GET_SIGNATURES
| PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
@@ -1001,16 +1005,7 @@
resolveStageDirLocked();
try {
- if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
- // TODO(b/118865310): Remove this when APEX validation is done via
- // StagingManager.
- validateApexInstallLocked(pkgInfo);
- } else {
- // Verify that stage looks sane with respect to existing application.
- // This currently only ensures packageName, versionCode, and certificate
- // consistency.
- validateApkInstallLocked(pkgInfo);
- }
+ validateApkInstallLocked(pkgInfo);
} catch (PackageManagerException e) {
throw e;
} catch (Throwable e) {
@@ -1301,54 +1296,6 @@
(params.installFlags & PackageManager.DONT_KILL_APP) != 0;
}
- @GuardedBy("mLock")
- private void validateApexInstallLocked(@Nullable PackageInfo pkgInfo)
- throws PackageManagerException {
- mResolvedStagedFiles.clear();
- mResolvedInheritedFiles.clear();
-
- try {
- resolveStageDirLocked();
- } catch (IOException e) {
- throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
- "Failed to resolve stage location", e);
- }
-
- final File[] addedFiles = mResolvedStageDir.listFiles(sAddedFilter);
- if (ArrayUtils.isEmpty(addedFiles)) {
- throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
- }
-
- if (addedFiles.length > 1) {
- throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
- "Only one APEX file at a time might be installed");
- }
- File addedFile = addedFiles[0];
- final ApkLite apk;
- try {
- apk = PackageParser.parseApkLite(
- addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
- } catch (PackageParserException e) {
- throw PackageManagerException.from(e);
- }
-
- mPackageName = apk.packageName;
- mVersionCode = apk.getLongVersionCode();
- mSigningDetails = apk.signingDetails;
- mResolvedBaseFile = addedFile;
-
- // STOPSHIP: Ensure that we remove the non-staged version of APEX installs in production
- // because we currently do not verify that signatures are consistent with the previously
- // installed version in that case.
- //
- // When that happens, this hack can be reverted and we can rely on APEXd to map between
- // APEX files and their package names instead of parsing it out of the AndroidManifest
- // such as here.
- if (params.appPackageName == null) {
- params.appPackageName = mPackageName;
- }
- }
-
/**
* Validate install by confirming that all application packages are have
* consistent package name, version code, and signing certificates.
@@ -1911,22 +1858,30 @@
}
@Override
- public void addChildSessionId(int sessionId) {
- final PackageInstallerSession session = mSessionProvider.getSession(sessionId);
- if (session == null) {
+ public void addChildSessionId(int childSessionId) {
+ final PackageInstallerSession childSession = mSessionProvider.getSession(childSessionId);
+ if (childSession == null) {
throw new RemoteException("Unable to add child.",
- new PackageManagerException("Child session " + sessionId + " does not exist"),
+ new PackageManagerException("Child session " + childSessionId
+ + " does not exist"),
+ false, true).rethrowAsRuntimeException();
+ }
+ // Session groups must be consistent wrt to isStaged parameter. Non-staging session
+ // cannot be grouped with staging sessions.
+ if (this.params.isStaged ^ childSession.params.isStaged) {
+ throw new RemoteException("Unable to add child.",
+ new PackageManagerException("Child session " + childSessionId
+ + " and parent session " + this.sessionId + " do not have consistent"
+ + " staging session settings."),
false, true).rethrowAsRuntimeException();
}
synchronized (mLock) {
- final int indexOfSession = mChildSessionIds.indexOfKey(sessionId);
+ final int indexOfSession = mChildSessionIds.indexOfKey(childSessionId);
if (indexOfSession >= 0) {
return;
}
- session.setParentSessionId(this.sessionId);
- // TODO: sanity check, if parent session is staged then child session should be
- // marked as staged.
- addChildSessionIdInternal(sessionId);
+ childSession.setParentSessionId(this.sessionId);
+ addChildSessionIdInternal(childSessionId);
}
}
@@ -2057,6 +2012,11 @@
return mStagedSessionFailed;
}
+ /** {@hide} */
+ @StagedSessionErrorCode int getStagedSessionErrorCode() {
+ return mStagedSessionErrorCode;
+ }
+
private void destroyInternal() {
synchronized (mLock) {
mSealed = true;
@@ -2221,35 +2181,6 @@
out.endTag(null, TAG_SESSION);
}
- private static String[] readGrantedRuntimePermissions(XmlPullParser in)
- throws IOException, XmlPullParserException {
- List<String> permissions = null;
-
- final int outerDepth = in.getDepth();
- int type;
- while ((type = in.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) {
- String permission = readStringAttribute(in, ATTR_NAME);
- if (permissions == null) {
- permissions = new ArrayList<>();
- }
- permissions.add(permission);
- }
- }
-
- if (permissions == null) {
- return null;
- }
-
- String[] permissionsArray = new String[permissions.size()];
- permissions.toArray(permissionsArray);
- return permissionsArray;
- }
-
// Sanity check to be performed when the session is restored from an external file. Only one
// of the session states should be true, or none of them.
private static boolean isStagedSessionStateValid(boolean isReady, boolean isApplied,
@@ -2273,8 +2204,6 @@
* @param sessionProvider
* @return The newly created session
*/
- // TODO(patb,109941548): modify readFromXml to consume to the next tag session tag so we
- // can have a complete session for the constructor
public static PackageInstallerSession readFromXml(@NonNull XmlPullParser in,
@NonNull PackageInstallerService.InternalCallback callback, @NonNull Context context,
@NonNull PackageManagerService pm, Looper installerThread,
@@ -2314,8 +2243,6 @@
params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON);
- params.grantedRuntimePermissions = readGrantedRuntimePermissions(in);
-
final File appIconFile = buildAppIconFile(sessionId, sessionsDir);
if (appIconFile.exists()) {
params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
@@ -2324,16 +2251,51 @@
final boolean isReady = readBooleanAttribute(in, ATTR_IS_READY);
final boolean isFailed = readBooleanAttribute(in, ATTR_IS_FAILED);
final boolean isApplied = readBooleanAttribute(in, ATTR_IS_APPLIED);
- final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE);
+ final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE,
+ SessionInfo.NO_ERROR);
if (!isStagedSessionStateValid(isReady, isApplied, isFailed)) {
throw new IllegalArgumentException("Can't restore staged session with invalid state.");
}
+ // Parse sub tags of this session, typically used for repeated values / arrays.
+ // Sub tags can come in any order, therefore we need to keep track of what we find while
+ // parsing and only set the right values at the end.
+
+ // Store the current depth. We should stop parsing when we reach an end tag at the same
+ // depth.
+ List<String> permissions = new ArrayList<>();
+ List<Integer> childSessionIds = new ArrayList<>();
+ int outerDepth = in.getDepth();
+ int type;
+ while ((type = in.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) {
+ permissions.add(readStringAttribute(in, ATTR_NAME));
+ }
+ if (TAG_CHILD_SESSION.equals(in.getName())) {
+ childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID));
+ }
+ }
+
+ if (permissions.size() > 0) {
+ params.grantedRuntimePermissions = permissions.stream().toArray(String[]::new);
+ }
+
+ int[] childSessionIdsArray;
+ if (childSessionIds.size() > 0) {
+ childSessionIdsArray = childSessionIds.stream().mapToInt(i -> i).toArray();
+ } else {
+ childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY;
+ }
+
return new PackageInstallerSession(callback, context, pm, sessionProvider,
installerThread, stagingManager, sessionId, userId, installerPackageName,
installerUid, params, createdMillis, stageDir, stageCid, prepared, sealed,
- EMPTY_CHILD_SESSION_ARRAY, parentSessionId, isReady, isFailed, isApplied,
+ childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
stagedSessionErrorCode);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 09fe26d..9100f6a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -42,6 +42,7 @@
import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.content.pm.PackageManager.INSTALL_ALLOW_DOWNGRADE;
import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
@@ -201,6 +202,7 @@
import android.content.pm.dex.ArtManager;
import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.dex.IArtManager;
+import android.content.rollback.IRollbackManager;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -10572,8 +10574,6 @@
Log.d(TAG, "Scanning package " + pkg.packageName);
}
- DexManager.maybeLogUnexpectedPackageDetails(pkg);
-
// Initialize package source and resource directories
final File scanFile = new File(pkg.codePath);
final File destCodeFile = new File(pkg.applicationInfo.getCodePath());
@@ -13872,6 +13872,38 @@
}
}
+ // If this is an update to a package that might be potentially downgraded, then we
+ // need to check with the rollback manager whether there's any userdata that might
+ // need to be restored for the package.
+ //
+ // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
+ if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
+ IRollbackManager rm = IRollbackManager.Stub.asInterface(
+ ServiceManager.getService(Context.ROLLBACK_SERVICE));
+
+ final String packageName = res.pkg.applicationInfo.packageName;
+ final String seInfo = res.pkg.applicationInfo.seInfo;
+ final PackageSetting ps;
+ int appId = -1;
+ long ceDataInode = -1;
+ synchronized (mSettings) {
+ ps = mSettings.getPackageLPr(packageName);
+ if (ps != null) {
+ appId = ps.appId;
+ ceDataInode = ps.getCeDataInode(userId);
+ }
+ }
+
+ if (ps != null) {
+ try {
+ rm.restoreUserData(packageName, userId, appId, ceDataInode, seInfo, token);
+ } catch (RemoteException re) {
+ // Cannot happen, the RollbackManager is hosted in the same process.
+ }
+ doRestore = true;
+ }
+ }
+
if (!doRestore) {
// No restore possible, or the Backup Manager was mysteriously not
// available -- just fire the post-install work request directly.
@@ -14569,6 +14601,9 @@
enableRollbackIntent.putExtra(
PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS,
installFlags);
+ enableRollbackIntent.putExtra(
+ PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS,
+ resolveUserIds(args.user.getIdentifier()));
enableRollbackIntent.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)),
PACKAGE_MIME_TYPE);
enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -20635,7 +20670,6 @@
storage.registerListener(mStorageListener);
mInstallerService.systemReady();
- mDexManager.systemReady();
mPackageDexOptimizer.systemReady();
getStorageManagerInternal().addExternalStoragePolicy(
@@ -23791,6 +23825,11 @@
}
return mArtManagerService.compileLayouts(pkg);
}
+
+ @Override
+ public void finishPackageInstall(int token, boolean didLaunch) {
+ PackageManagerService.this.finishPackageInstall(token, didLaunch);
+ }
}
@GuardedBy("mPackages")
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 022c1aa..692c032 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -40,10 +40,10 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionParams;
-import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ApkLite;
import android.content.pm.PackageParser.PackageLite;
@@ -634,9 +634,9 @@
if (showVersionCode) {
pw.print(" versionCode:");
if (info.applicationInfo != null) {
- pw.print(info.applicationInfo.versionCode);
+ pw.print(info.applicationInfo.longVersionCode);
} else {
- pw.print(info.versionCode);
+ pw.print(info.getLongVersionCode());
}
}
if (listInstaller && !isApex) {
@@ -2307,7 +2307,7 @@
sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK;
break;
case "--apex":
- sessionParams.installFlags |= PackageManager.INSTALL_APEX;
+ sessionParams.setInstallAsApex();
sessionParams.setStaged();
break;
case "--multi-package":
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 7bab0bb..5311c2a 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -41,7 +41,9 @@
import com.android.internal.os.BackgroundThread;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.stream.Collectors;
/**
* This class handles staged install sessions, i.e. install sessions that require packages to
@@ -126,12 +128,24 @@
return false;
}
- private static boolean submitSessionToApexService(int sessionId, ApexInfoList apexInfoList) {
+ private static boolean submitSessionToApexService(@NonNull PackageInstallerSession session,
+ List<PackageInstallerSession> childSessions,
+ ApexInfoList apexInfoList) {
+ return sendSubmitStagedSessionRequest(
+ session.sessionId,
+ childSessions != null
+ ? childSessions.stream().mapToInt(s -> s.sessionId).toArray() :
+ new int[]{},
+ apexInfoList);
+ }
+
+ private static boolean sendSubmitStagedSessionRequest(
+ int sessionId, int[] childSessionIds, ApexInfoList apexInfoList) {
final IApexService apex = IApexService.Stub.asInterface(
ServiceManager.getService("apexservice"));
boolean success;
try {
- success = apex.submitStagedSession(sessionId, new int[0], apexInfoList);
+ success = apex.submitStagedSession(sessionId, childSessionIds, apexInfoList);
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
return false;
@@ -139,31 +153,49 @@
return success;
}
+ private static boolean isApexSession(@NonNull PackageInstallerSession session) {
+ return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
+ }
+
private void preRebootVerification(@NonNull PackageInstallerSession session) {
boolean success = true;
- if ((session.params.installFlags & PackageManager.INSTALL_APEX) != 0) {
- final ApexInfoList apexInfoList = new ApexInfoList();
+ final ApexInfoList apexInfoList = new ApexInfoList();
+ // APEX checks. For single-package sessions, check if they contain an APEX. For
+ // multi-package sessions, find all the child sessions that contain an APEX.
+ if (!session.isMultiPackage()
+ && isApexSession(session)) {
+ success = submitSessionToApexService(session, null, apexInfoList);
+ } else if (session.isMultiPackage()) {
+ List<PackageInstallerSession> childSessions =
+ Arrays.stream(session.getChildSessionIds())
+ // Retrieve cached sessions matching ids.
+ .mapToObj(i -> mStagedSessions.get(i))
+ // Filter only the ones containing APEX.
+ .filter(childSession -> isApexSession(childSession))
+ .collect(Collectors.toList());
+ if (!childSessions.isEmpty()) {
+ success = submitSessionToApexService(session, childSessions, apexInfoList);
+ } // else this is a staged multi-package session with no APEX files.
+ }
- if (!submitSessionToApexService(session.sessionId, apexInfoList)) {
- success = false;
- } else {
- // For APEXes, we validate the signature here before we mark the session as ready,
- // so we fail the session early if there is a signature mismatch. For APKs, the
- // signature verification will be done by the package manager at the point at which
- // it applies the staged install.
- //
- // TODO: Decide whether we want to fail fast by detecting signature mismatches right
- // away.
- for (ApexInfo apexPackage : apexInfoList.apexInfos) {
- if (!validateApexSignatureLocked(apexPackage.packagePath,
- apexPackage.packageName)) {
- success = false;
- break;
- }
+ if (success && (apexInfoList.apexInfos.length > 0)) {
+ // For APEXes, we validate the signature here before we mark the session as ready,
+ // so we fail the session early if there is a signature mismatch. For APKs, the
+ // signature verification will be done by the package manager at the point at which
+ // it applies the staged install.
+ //
+ // TODO: Decide whether we want to fail fast by detecting signature mismatches for APKs,
+ // right away.
+ for (ApexInfo apexPackage : apexInfoList.apexInfos) {
+ if (!validateApexSignatureLocked(apexPackage.packagePath,
+ apexPackage.packageName)) {
+ success = false;
+ break;
}
}
}
+
if (success) {
session.setStagedSessionReady();
} else {
@@ -206,15 +238,59 @@
}
}
- void abortSession(@NonNull PackageInstallerSession sessionInfo) {
- updateStoredSession(sessionInfo);
+ void abortSession(@NonNull PackageInstallerSession session) {
synchronized (mStagedSessions) {
- mStagedSessions.remove(sessionInfo.sessionId);
+ updateStoredSession(session);
+ mStagedSessions.remove(session.sessionId);
}
}
+ @GuardedBy("mStagedSessions")
+ private boolean isMultiPackageSessionComplete(@NonNull PackageInstallerSession session) {
+ // This method assumes that the argument is either a parent session of a multi-package
+ // i.e. isMultiPackage() returns true, or that it is a child session, i.e.
+ // hasParentSessionId() returns true.
+ if (session.isMultiPackage()) {
+ // Parent session of a multi-package group. Check that we restored all the children.
+ for (int childSession : session.getChildSessionIds()) {
+ if (mStagedSessions.get(childSession) == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+ if (session.hasParentSessionId()) {
+ PackageInstallerSession parent = mStagedSessions.get(session.getParentSessionId());
+ if (parent == null) {
+ return false;
+ }
+ return isMultiPackageSessionComplete(parent);
+ }
+ Slog.wtf(TAG, "Attempting to restore an invalid multi-package session.");
+ return false;
+ }
+
void restoreSession(@NonNull PackageInstallerSession session) {
- updateStoredSession(session);
+ PackageInstallerSession sessionToResume = session;
+ synchronized (mStagedSessions) {
+ mStagedSessions.append(session.sessionId, session);
+ // For multi-package sessions, we don't know in which order they will be restored. We
+ // need to wait until we have restored all the session in a group before restoring them.
+ if (session.isMultiPackage() || session.hasParentSessionId()) {
+ if (!isMultiPackageSessionComplete(session)) {
+ // Still haven't recovered all sessions of the group, return.
+ return;
+ }
+ // Group recovered, find the parent if necessary and resume the installation.
+ if (session.hasParentSessionId()) {
+ sessionToResume = mStagedSessions.get(session.getParentSessionId());
+ }
+ }
+ }
+ checkStateAndResume(sessionToResume);
+ }
+
+ private void checkStateAndResume(@NonNull PackageInstallerSession session) {
// Check the state of the session and decide what to do next.
if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) {
// Final states, nothing to do.
@@ -227,6 +303,8 @@
} else {
// Session had already being marked ready. Start the checks to verify if there is any
// follow-up work.
+ // TODO(b/118865310): should this be synchronous to ensure it completes before
+ // systemReady() finishes?
mBgHandler.post(() -> resumeSession(session));
}
}
diff --git a/services/core/java/com/android/server/pm/dex/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java
index 78fa82c..59cc0cf 100644
--- a/services/core/java/com/android/server/pm/dex/DexLogger.java
+++ b/services/core/java/com/android/server/pm/dex/DexLogger.java
@@ -16,11 +16,15 @@
package com.android.server.pm.dex;
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX;
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_NATIVE;
+
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.os.FileUtils;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.util.ByteStringUtils;
import android.util.EventLog;
@@ -35,20 +39,23 @@
import com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode;
import java.io.File;
+import java.io.IOException;
import java.util.Map;
import java.util.Set;
/**
- * This class is responsible for logging data about secondary dex files.
- * The data logged includes hashes of the name and content of each file.
+ * This class is responsible for logging data about secondary dex files and, despite the name,
+ * native code executed from an app's private directory. The data logged includes hashes of the
+ * name and content of each file.
*/
public class DexLogger {
private static final String TAG = "DexLogger";
- // Event log tag & subtag used for SafetyNet logging of dynamic
- // code loading (DCL) - see b/63927552.
+ // Event log tag & subtags used for SafetyNet logging of dynamic code loading (DCL) -
+ // see b/63927552.
private static final int SNET_TAG = 0x534e4554;
- private static final String DCL_SUBTAG = "dcl";
+ private static final String DCL_DEX_SUBTAG = "dcl";
+ private static final String DCL_NATIVE_SUBTAG = "dcln";
private final IPackageManager mPackageManager;
private final PackageDynamicCodeLoading mPackageDynamicCodeLoading;
@@ -114,12 +121,11 @@
}
int storageFlags;
- if (appInfo.deviceProtectedDataDir != null
- && FileUtils.contains(appInfo.deviceProtectedDataDir, filePath)) {
- storageFlags = StorageManager.FLAG_STORAGE_DE;
- } else if (appInfo.credentialProtectedDataDir != null
- && FileUtils.contains(appInfo.credentialProtectedDataDir, filePath)) {
+
+ if (fileIsUnder(filePath, appInfo.credentialProtectedDataDir)) {
storageFlags = StorageManager.FLAG_STORAGE_CE;
+ } else if (fileIsUnder(filePath, appInfo.deviceProtectedDataDir)) {
+ storageFlags = StorageManager.FLAG_STORAGE_DE;
} else {
Slog.e(TAG, "Could not infer CE/DE storage for path " + filePath);
needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId);
@@ -139,6 +145,9 @@
+ ": " + e.getMessage());
}
+ String subtag = fileInfo.mFileType == FILE_TYPE_DEX
+ ? DCL_DEX_SUBTAG
+ : DCL_NATIVE_SUBTAG;
String fileName = new File(filePath).getName();
String message = PackageUtils.computeSha256Digest(fileName.getBytes());
@@ -165,7 +174,7 @@
}
if (loadingUid != -1) {
- writeDclEvent(loadingUid, message);
+ writeDclEvent(subtag, loadingUid, message);
}
}
}
@@ -175,21 +184,58 @@
}
}
+ private boolean fileIsUnder(String filePath, String directoryPath) {
+ if (directoryPath == null) {
+ return false;
+ }
+
+ try {
+ return FileUtils.contains(new File(directoryPath).getCanonicalPath(),
+ new File(filePath).getCanonicalPath());
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
@VisibleForTesting
PackageDynamicCode getPackageDynamicCodeInfo(String packageName) {
return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName);
}
@VisibleForTesting
- void writeDclEvent(int uid, String message) {
- EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, uid, message);
+ void writeDclEvent(String subtag, int uid, String message) {
+ EventLog.writeEvent(SNET_TAG, subtag, uid, message);
}
- void record(int loaderUserId, String dexPath,
- String owningPackageName, String loadingPackageName) {
+ void recordDex(int loaderUserId, String dexPath, String owningPackageName,
+ String loadingPackageName) {
if (mPackageDynamicCodeLoading.record(owningPackageName, dexPath,
- PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId,
- loadingPackageName)) {
+ FILE_TYPE_DEX, loaderUserId, loadingPackageName)) {
+ mPackageDynamicCodeLoading.maybeWriteAsync();
+ }
+ }
+
+ /**
+ * Record that an app running in the specified uid has executed native code from the file at
+ * {@link path}.
+ */
+ public void recordNative(int loadingUid, String path) {
+ String[] packages;
+ try {
+ packages = mPackageManager.getPackagesForUid(loadingUid);
+ if (packages == null || packages.length == 0) {
+ return;
+ }
+ } catch (RemoteException e) {
+ // Can't happen, we're local.
+ return;
+ }
+
+ String loadingPackageName = packages[0];
+ int loadingUserId = UserHandle.getUserId(loadingUid);
+
+ if (mPackageDynamicCodeLoading.record(loadingPackageName, path,
+ FILE_TYPE_NATIVE, loadingUserId, loadingPackageName)) {
mPackageDynamicCodeLoading.maybeWriteAsync();
}
}
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index b546836..7ac7395 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -16,31 +16,28 @@
package com.android.server.pm.dex;
+import static android.provider.DeviceConfig.FsiBoot;
+
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageParser;
-import android.database.ContentObserver;
-import android.os.Build;
import android.os.FileUtils;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.storage.StorageManager;
-import android.provider.Settings.Global;
+import android.provider.DeviceConfig;
import android.util.Log;
import android.util.Slog;
import android.util.jar.StrictJarFile;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.PackageDexOptimizer;
@@ -136,10 +133,6 @@
return mDexLogger;
}
- public void systemReady() {
- registerSettingObserver();
- }
-
/**
* Notify about dex files loads.
* Note that this method is invoked when apps load dex files and it should
@@ -235,7 +228,7 @@
continue;
}
- mDexLogger.record(loaderUserId, dexPath, searchResult.mOwningPackageName,
+ mDexLogger.recordDex(loaderUserId, dexPath, searchResult.mOwningPackageName,
loadingAppInfo.packageName);
if (classLoaderContexts != null) {
@@ -699,47 +692,10 @@
mDexLogger.writeNow();
}
- private void registerSettingObserver() {
- final ContentResolver resolver = mContext.getContentResolver();
-
- // This observer provides a one directional mapping from Global.PRIV_APP_OOB_ENABLED to
- // pm.dexopt.priv-apps-oob property. This is only for experiment and should be removed once
- // it is done.
- ContentObserver privAppOobObserver = new ContentObserver(null) {
- @Override
- public void onChange(boolean selfChange) {
- int oobEnabled = Global.getInt(resolver, Global.PRIV_APP_OOB_ENABLED, 0);
- SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB,
- oobEnabled == 1 ? "true" : "false");
- }
- };
- resolver.registerContentObserver(
- Global.getUriFor(Global.PRIV_APP_OOB_ENABLED), false, privAppOobObserver,
- UserHandle.USER_SYSTEM);
- // At boot, restore the value from the setting, which persists across reboot.
- privAppOobObserver.onChange(true);
-
- ContentObserver privAppOobListObserver = new ContentObserver(null) {
- @Override
- public void onChange(boolean selfChange) {
- String oobList = Global.getString(resolver, Global.PRIV_APP_OOB_LIST);
- if (oobList == null) {
- oobList = "ALL";
- }
- SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, oobList);
- }
- };
- resolver.registerContentObserver(
- Global.getUriFor(Global.PRIV_APP_OOB_LIST), false, privAppOobListObserver,
- UserHandle.USER_SYSTEM);
- // At boot, restore the value from the setting, which persists across reboot.
- privAppOobListObserver.onChange(true);
- }
-
/**
* Returns whether the given package is in the list of privilaged apps that should run out of
- * box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that when
- * the the OOB list is empty, all priv apps will run in OOB mode.
+ * box. This only makes sense if the feature is enabled. Note that when the the OOB list is
+ * empty, all priv apps will run in OOB mode.
*/
public static boolean isPackageSelectedToRunOob(String packageName) {
return isPackageSelectedToRunOob(Arrays.asList(packageName));
@@ -747,19 +703,35 @@
/**
* Returns whether any of the given packages are in the list of privilaged apps that should run
- * out of box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that
- * when the the OOB list is empty, all priv apps will run in OOB mode.
+ * out of box. This only makes sense if the feature is enabled. Note that when the the OOB list
+ * is empty, all priv apps will run in OOB mode.
*/
public static boolean isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess) {
- if (!SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false)) {
+ return isPackageSelectedToRunOobInternal(
+ SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false),
+ SystemProperties.get(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL"),
+ DeviceConfig.getProperty(FsiBoot.NAMESPACE, FsiBoot.OOB_ENABLED),
+ DeviceConfig.getProperty(FsiBoot.NAMESPACE, FsiBoot.OOB_WHITELIST),
+ packageNamesInSameProcess);
+ }
+
+ @VisibleForTesting
+ /* package */ static boolean isPackageSelectedToRunOobInternal(
+ boolean isDefaultEnabled, String defaultWhitelist, String overrideEnabled,
+ String overrideWhitelist, Collection<String> packageNamesInSameProcess) {
+ // Allow experiment (if exists) to override device configuration.
+ boolean enabled = overrideEnabled != null ? overrideEnabled.equals("true")
+ : isDefaultEnabled;
+ if (!enabled) {
return false;
}
- String oobListProperty = SystemProperties.get(
- PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL");
- if ("ALL".equals(oobListProperty)) {
+
+ // Similarly, experiment flag can override the whitelist.
+ String whitelist = overrideWhitelist != null ? overrideWhitelist : defaultWhitelist;
+ if ("ALL".equals(whitelist)) {
return true;
}
- for (String oobPkgName : oobListProperty.split(",")) {
+ for (String oobPkgName : whitelist.split(",")) {
if (packageNamesInSameProcess.contains(oobPkgName)) {
return true;
}
@@ -768,32 +740,6 @@
}
/**
- * Generates package related log if the package has code stored in unexpected way.
- */
- public static void maybeLogUnexpectedPackageDetails(PackageParser.Package pkg) {
- if (!Build.IS_DEBUGGABLE) {
- return;
- }
-
- if (pkg.isPrivileged() && isPackageSelectedToRunOob(pkg.packageName)) {
- logIfPackageHasUncompressedCode(pkg);
- }
- }
-
- /**
- * Generates log if the APKs in the given package have uncompressed dex file and so
- * files that can be direclty mapped.
- */
- private static void logIfPackageHasUncompressedCode(PackageParser.Package pkg) {
- auditUncompressedCodeInApk(pkg.baseCodePath);
- if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
- for (int i = 0; i < pkg.splitCodePaths.length; i++) {
- auditUncompressedCodeInApk(pkg.splitCodePaths[i]);
- }
- }
- }
-
- /**
* Generates log if the archive located at {@code fileName} has uncompressed dex file and so
* files that can be direclty mapped.
*/
diff --git a/services/core/java/com/android/server/pm/dex/OWNERS b/services/core/java/com/android/server/pm/dex/OWNERS
new file mode 100644
index 0000000..fcc1f6c
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/OWNERS
@@ -0,0 +1,4 @@
+agampe@google.com
+calin@google.com
+ngeoffray@google.com
+sehr@google.com
diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
index 6d4bc82..cc26c9b 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
@@ -53,6 +53,9 @@
// is represented in the text file format.)
static final int FILE_TYPE_DEX = 'D';
+ // Type code to indicate a secondary file containing native code.
+ static final int FILE_TYPE_NATIVE = 'N';
+
private static final String TAG = "PackageDynamicCodeLoading";
private static final String FILE_VERSION_HEADER = "DCL1";
@@ -107,7 +110,7 @@
*/
boolean record(String owningPackageName, String filePath, int fileType, int ownerUserId,
String loadingPackageName) {
- if (fileType != FILE_TYPE_DEX) {
+ if (!isValidFileType(fileType)) {
throw new IllegalArgumentException("Bad file type: " + fileType);
}
synchronized (mLock) {
@@ -120,6 +123,10 @@
}
}
+ private static boolean isValidFileType(int fileType) {
+ return fileType == FILE_TYPE_DEX || fileType == FILE_TYPE_NATIVE;
+ }
+
/**
* Return all packages that contain records of secondary dex files. (Note that data updates
* asynchronously, so {@link #getPackageDynamicCodeInfo} may still return null if passed
@@ -407,7 +414,7 @@
if (packages.length == 0) {
throw new IOException("Malformed line: " + line);
}
- if (type != FILE_TYPE_DEX) {
+ if (!isValidFileType(type)) {
throw new IOException("Unknown file type: " + line);
}
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 d5af313..20d6d4e 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1290,6 +1290,7 @@
return mContext.getPackageManager().getPackageInfo(pkg,
DEFAULT_PACKAGE_INFO_QUERY_FLAGS | extraFlags);
} catch (NameNotFoundException e) {
+ Slog.e(TAG, "PackageNot found: " + pkg, e);
return null;
}
}
diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS
index 88b97ea..01dc01e 100644
--- a/services/core/java/com/android/server/pm/permission/OWNERS
+++ b/services/core/java/com/android/server/pm/permission/OWNERS
@@ -1,4 +1,4 @@
-per-file DefaultPermissionGrantPolicy.java = bpoiesz@google.com
+moltmann@google.com
per-file DefaultPermissionGrantPolicy.java = hackbod@android.com
per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com
per-file DefaultPermissionGrantPolicy.java = svetoslavganov@google.com
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 41cab2d..0e40a00 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1901,9 +1901,8 @@
mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
@Override
- public int onAppTransitionStartingLocked(int transit, IBinder openToken,
- IBinder closeToken, long duration, long statusBarAnimationStartTime,
- long statusBarAnimationDuration) {
+ public int onAppTransitionStartingLocked(int transit, long duration,
+ long statusBarAnimationStartTime, long statusBarAnimationDuration) {
return handleStartTransitionForKeyguardLw(transit, duration);
}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index 1d74350..ab2807a 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -104,6 +104,7 @@
public static final int REASON_SETTING_CHANGED = 8;
public static final int REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON = 9;
public static final int REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_OFF = 10;
+ public static final int REASON_STICKY_RESTORE_OFF = 13;
/**
* Plugin interface. All methods are guaranteed to be called on the same (handler) thread.
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index b7f28da..404e450 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -28,7 +28,6 @@
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
-import android.provider.Settings.Global;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -85,42 +84,56 @@
@GuardedBy("mLock")
private boolean mIsBatteryLevelLow;
- /** Previously known value of Global.LOW_POWER_MODE. */
+ /** Previously known value of Settings.Global.LOW_POWER_MODE. */
@GuardedBy("mLock")
private boolean mSettingBatterySaverEnabled;
- /** Previously known value of Global.LOW_POWER_MODE_STICKY. */
+ /** Previously known value of Settings.Global.LOW_POWER_MODE_STICKY. */
@GuardedBy("mLock")
private boolean mSettingBatterySaverEnabledSticky;
/** Config flag to track if battery saver's sticky behaviour is disabled. */
private final boolean mBatterySaverStickyBehaviourDisabled;
+ /**
+ * Whether or not to end sticky battery saver upon reaching a level specified by
+ * {@link #mSettingBatterySaverStickyAutoDisableThreshold}.
+ */
+ @GuardedBy("mLock")
+ private boolean mSettingBatterySaverStickyAutoDisableEnabled;
+
+ /**
+ * The battery level at which to end sticky battery saver. Only useful if
+ * {@link #mSettingBatterySaverStickyAutoDisableEnabled} is {@code true}.
+ */
+ @GuardedBy("mLock")
+ private int mSettingBatterySaverStickyAutoDisableThreshold;
+
/** Config flag to track default disable threshold for Dynamic Power Savings enabled battery
* saver. */
@GuardedBy("mLock")
private final int mDynamicPowerSavingsDefaultDisableThreshold;
/**
- * Previously known value of Global.LOW_POWER_MODE_TRIGGER_LEVEL.
+ * Previously known value of Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL.
* (Currently only used in dumpsys.)
*/
@GuardedBy("mLock")
private int mSettingBatterySaverTriggerThreshold;
- /** Previously known value of Global.AUTOMATIC_POWER_SAVER_MODE. */
+ /** Previously known value of Settings.Global.AUTOMATIC_POWER_SAVER_MODE. */
@GuardedBy("mLock")
private int mSettingAutomaticBatterySaver;
/** When to disable battery saver again if it was enabled due to an external suggestion.
- * Corresponds to Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD.
+ * Corresponds to Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD.
*/
@GuardedBy("mLock")
private int mDynamicPowerSavingsDisableThreshold;
/**
* Whether we've received a suggestion that battery saver should be on from an external app.
- * Updates when Global.DYNAMIC_POWER_SAVINGS_ENABLED changes.
+ * Updates when Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED changes.
*/
@GuardedBy("mLock")
private boolean mDynamicPowerSavingsBatterySaver;
@@ -181,7 +194,7 @@
Slog.d(TAG, "onBootCompleted");
}
// Just booted. We don't want LOW_POWER_MODE to be persisted, so just always clear it.
- putGlobalSetting(Global.LOW_POWER_MODE, 0);
+ putGlobalSetting(Settings.Global.LOW_POWER_MODE, 0);
// This is called with the power manager lock held. Don't do anything that may call to
// upper services. (e.g. don't call into AM directly)
@@ -199,13 +212,19 @@
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
false, mSettingsObserver, UserHandle.USER_SYSTEM);
cr.registerContentObserver(Settings.Global.getUriFor(
- Global.AUTOMATIC_POWER_SAVER_MODE),
+ Settings.Global.AUTOMATIC_POWER_SAVER_MODE),
false, mSettingsObserver, UserHandle.USER_SYSTEM);
cr.registerContentObserver(Settings.Global.getUriFor(
- Global.DYNAMIC_POWER_SAVINGS_ENABLED),
+ Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED),
false, mSettingsObserver, UserHandle.USER_SYSTEM);
cr.registerContentObserver(Settings.Global.getUriFor(
- Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD),
+ Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD),
+ false, mSettingsObserver, UserHandle.USER_SYSTEM);
+ cr.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED),
+ false, mSettingsObserver, UserHandle.USER_SYSTEM);
+ cr.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL),
false, mSettingsObserver, UserHandle.USER_SYSTEM);
synchronized (mLock) {
@@ -239,25 +258,31 @@
}
@GuardedBy("mLock")
- void refreshSettingsLocked() {
+ private void refreshSettingsLocked() {
final boolean lowPowerModeEnabled = getGlobalSetting(
Settings.Global.LOW_POWER_MODE, 0) != 0;
final boolean lowPowerModeEnabledSticky = getGlobalSetting(
Settings.Global.LOW_POWER_MODE_STICKY, 0) != 0;
final boolean dynamicPowerSavingsBatterySaver = getGlobalSetting(
- Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0) != 0;
+ Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0) != 0;
final int lowPowerModeTriggerLevel = getGlobalSetting(
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
- final int automaticBatterySaver = getGlobalSetting(
- Global.AUTOMATIC_POWER_SAVER_MODE,
+ final int automaticBatterySaverMode = getGlobalSetting(
+ Settings.Global.AUTOMATIC_POWER_SAVER_MODE,
PowerManager.POWER_SAVER_MODE_PERCENTAGE);
final int dynamicPowerSavingsDisableThreshold = getGlobalSetting(
- Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
+ Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
mDynamicPowerSavingsDefaultDisableThreshold);
+ final boolean isStickyAutoDisableEnabled = getGlobalSetting(
+ Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 0) != 0;
+ final int stickyAutoDisableThreshold = getGlobalSetting(
+ Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90);
setSettingsLocked(lowPowerModeEnabled, lowPowerModeEnabledSticky,
- lowPowerModeTriggerLevel, automaticBatterySaver, dynamicPowerSavingsBatterySaver,
- dynamicPowerSavingsDisableThreshold);
+ lowPowerModeTriggerLevel,
+ isStickyAutoDisableEnabled, stickyAutoDisableThreshold,
+ automaticBatterySaverMode,
+ dynamicPowerSavingsBatterySaver, dynamicPowerSavingsDisableThreshold);
}
/**
@@ -269,12 +294,16 @@
@GuardedBy("mLock")
@VisibleForTesting
void setSettingsLocked(boolean batterySaverEnabled, boolean batterySaverEnabledSticky,
- int batterySaverTriggerThreshold, int automaticBatterySaver,
+ int batterySaverTriggerThreshold,
+ boolean isStickyAutoDisableEnabled, int stickyAutoDisableThreshold,
+ int automaticBatterySaver,
boolean dynamicPowerSavingsBatterySaver, int dynamicPowerSavingsDisableThreshold) {
if (DEBUG) {
Slog.d(TAG, "setSettings: enabled=" + batterySaverEnabled
+ " sticky=" + batterySaverEnabledSticky
+ " threshold=" + batterySaverTriggerThreshold
+ + " stickyAutoDisableEnabled=" + isStickyAutoDisableEnabled
+ + " stickyAutoDisableThreshold=" + stickyAutoDisableThreshold
+ " automaticBatterySaver=" + automaticBatterySaver
+ " dynamicPowerSavingsBatterySaver=" + dynamicPowerSavingsBatterySaver
+ " dynamicPowerSavingsDisableThreshold="
@@ -283,11 +312,19 @@
mSettingsLoaded = true;
+ // Set sensible limits.
+ stickyAutoDisableThreshold = Math.max(stickyAutoDisableThreshold,
+ batterySaverTriggerThreshold);
+
final boolean enabledChanged = mSettingBatterySaverEnabled != batterySaverEnabled;
final boolean stickyChanged =
mSettingBatterySaverEnabledSticky != batterySaverEnabledSticky;
final boolean thresholdChanged
= mSettingBatterySaverTriggerThreshold != batterySaverTriggerThreshold;
+ final boolean stickyAutoDisableEnabledChanged =
+ mSettingBatterySaverStickyAutoDisableEnabled != isStickyAutoDisableEnabled;
+ final boolean stickyAutoDisableThresholdChanged =
+ mSettingBatterySaverStickyAutoDisableThreshold != stickyAutoDisableThreshold;
final boolean automaticModeChanged = mSettingAutomaticBatterySaver != automaticBatterySaver;
final boolean dynamicPowerSavingsThresholdChanged =
mDynamicPowerSavingsDisableThreshold != dynamicPowerSavingsDisableThreshold;
@@ -295,6 +332,7 @@
mDynamicPowerSavingsBatterySaver != dynamicPowerSavingsBatterySaver;
if (!(enabledChanged || stickyChanged || thresholdChanged || automaticModeChanged
+ || stickyAutoDisableEnabledChanged || stickyAutoDisableThresholdChanged
|| dynamicPowerSavingsThresholdChanged || dynamicPowerSavingsBatterySaverChanged)) {
return;
}
@@ -302,6 +340,8 @@
mSettingBatterySaverEnabled = batterySaverEnabled;
mSettingBatterySaverEnabledSticky = batterySaverEnabledSticky;
mSettingBatterySaverTriggerThreshold = batterySaverTriggerThreshold;
+ mSettingBatterySaverStickyAutoDisableEnabled = isStickyAutoDisableEnabled;
+ mSettingBatterySaverStickyAutoDisableThreshold = stickyAutoDisableThreshold;
mSettingAutomaticBatterySaver = automaticBatterySaver;
mDynamicPowerSavingsDisableThreshold = dynamicPowerSavingsDisableThreshold;
mDynamicPowerSavingsBatterySaver = dynamicPowerSavingsBatterySaver;
@@ -376,7 +416,9 @@
+ " mBatterySaverSnoozing=" + mBatterySaverSnoozing
+ " mIsPowered=" + mIsPowered
+ " mSettingAutomaticBatterySaver=" + mSettingAutomaticBatterySaver
- + " mSettingBatterySaverEnabledSticky=" + mSettingBatterySaverEnabledSticky);
+ + " mSettingBatterySaverEnabledSticky=" + mSettingBatterySaverEnabledSticky
+ + " mSettingBatterySaverStickyAutoDisableEnabled="
+ + mSettingBatterySaverStickyAutoDisableEnabled);
}
if (!(mBootCompleted && mSettingsLoaded && mBatteryStatusSet)) {
return; // Not fully initialized yet.
@@ -392,10 +434,15 @@
"Plugged in");
} else if (mSettingBatterySaverEnabledSticky && !mBatterySaverStickyBehaviourDisabled) {
- // Re-enable BS.
- enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ true,
- BatterySaverController.REASON_STICKY_RESTORE,
- "Sticky restore");
+ if (mSettingBatterySaverStickyAutoDisableEnabled
+ && mBatteryLevel >= mSettingBatterySaverStickyAutoDisableThreshold) {
+ setStickyActive(false);
+ } else {
+ // Re-enable BS.
+ enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ true,
+ BatterySaverController.REASON_STICKY_RESTORE,
+ "Sticky restore");
+ }
} else if (mSettingAutomaticBatterySaver
== PowerManager.POWER_SAVER_MODE_PERCENTAGE
@@ -483,12 +530,10 @@
}
mSettingBatterySaverEnabled = enable;
- putGlobalSetting(Global.LOW_POWER_MODE, enable ? 1 : 0);
+ putGlobalSetting(Settings.Global.LOW_POWER_MODE, enable ? 1 : 0);
if (manual) {
- mSettingBatterySaverEnabledSticky = !mBatterySaverStickyBehaviourDisabled && enable;
- putGlobalSetting(Global.LOW_POWER_MODE_STICKY,
- mSettingBatterySaverEnabledSticky ? 1 : 0);
+ setStickyActive(!mBatterySaverStickyBehaviourDisabled && enable);
}
mBatterySaverController.enableBatterySaver(enable, intReason);
@@ -506,7 +551,8 @@
}
}
- private void triggerDynamicModeNotification() {
+ @VisibleForTesting
+ void triggerDynamicModeNotification() {
NotificationManager manager = mContext.getSystemService(NotificationManager.class);
ensureNotificationChannelExists(manager);
@@ -553,14 +599,20 @@
mBatterySaverSnoozing = snoozing;
}
+ private void setStickyActive(boolean active) {
+ mSettingBatterySaverEnabledSticky = active;
+ putGlobalSetting(Settings.Global.LOW_POWER_MODE_STICKY,
+ mSettingBatterySaverEnabledSticky ? 1 : 0);
+ }
+
@VisibleForTesting
protected void putGlobalSetting(String key, int value) {
- Global.putInt(mContext.getContentResolver(), key, value);
+ Settings.Global.putInt(mContext.getContentResolver(), key, value);
}
@VisibleForTesting
protected int getGlobalSetting(String key, int defValue) {
- return Global.getInt(mContext.getContentResolver(), key, defValue);
+ return Settings.Global.getInt(mContext.getContentResolver(), key, defValue);
}
public void dump(PrintWriter pw) {
@@ -597,6 +649,10 @@
pw.println(mSettingBatterySaverEnabled);
pw.print(" mSettingBatterySaverEnabledSticky=");
pw.println(mSettingBatterySaverEnabledSticky);
+ pw.print(" mSettingBatterySaverStickyAutoDisableEnabled=");
+ pw.println(mSettingBatterySaverStickyAutoDisableEnabled);
+ pw.print(" mSettingBatterySaverStickyAutoDisableThreshold=");
+ pw.println(mSettingBatterySaverStickyAutoDisableThreshold);
pw.print(" mSettingBatterySaverTriggerThreshold=");
pw.println(mSettingBatterySaverTriggerThreshold);
pw.print(" mBatterySaverStickyBehaviourDisabled=");
@@ -628,6 +684,13 @@
mSettingBatterySaverEnabledSticky);
proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_TRIGGER_THRESHOLD,
mSettingBatterySaverTriggerThreshold);
+ proto.write(
+ BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_STICKY_AUTO_DISABLE_ENABLED,
+ mSettingBatterySaverStickyAutoDisableEnabled);
+ proto.write(
+ BatterySaverStateMachineProto
+ .SETTING_BATTERY_SAVER_STICKY_AUTO_DISABLE_THRESHOLD,
+ mSettingBatterySaverStickyAutoDisableThreshold);
proto.end(token);
}
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index 5516b23..c0517fd 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -342,10 +342,21 @@
@WorkerThread
private void notifyRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId);
- if (listeners == null) {
- return;
+ if (listeners != null) {
+ notifyRoleHoldersChangedForListeners(listeners, roleName, userId);
}
+ RemoteCallbackList<IOnRoleHoldersChangedListener> allUserListeners = getListeners(
+ UserHandle.USER_ALL);
+ if (allUserListeners != null) {
+ notifyRoleHoldersChangedForListeners(allUserListeners, roleName, userId);
+ }
+ }
+
+ @WorkerThread
+ private void notifyRoleHoldersChangedForListeners(
+ @NonNull RemoteCallbackList<IOnRoleHoldersChangedListener> listeners,
+ @NonNull String roleName, @UserIdInt int userId) {
int broadcastCount = listeners.beginBroadcast();
try {
for (int i = 0; i < broadcastCount; i++) {
@@ -395,7 +406,7 @@
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return Collections.emptyList();
}
- userId = handleIncomingUser(userId, "getRoleHoldersAsUser");
+ userId = handleIncomingUser(userId, "getRoleHoldersAsUser", false);
getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
"getRoleHoldersAsUser");
@@ -423,7 +434,7 @@
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return;
}
- userId = handleIncomingUser(userId, "addRoleHolderAsUser");
+ userId = handleIncomingUser(userId, "addRoleHolderAsUser", false);
getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
"addRoleHolderAsUser");
@@ -440,7 +451,7 @@
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return;
}
- userId = handleIncomingUser(userId, "removeRoleHolderAsUser");
+ userId = handleIncomingUser(userId, "removeRoleHolderAsUser", false);
getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
"removeRoleHolderAsUser");
@@ -457,7 +468,7 @@
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return;
}
- userId = handleIncomingUser(userId, "clearRoleHoldersAsUser");
+ userId = handleIncomingUser(userId, "clearRoleHoldersAsUser", false);
getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
"clearRoleHoldersAsUser");
@@ -468,11 +479,12 @@
public void addOnRoleHoldersChangedListenerAsUser(
@NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
Preconditions.checkNotNull(listener, "listener cannot be null");
- if (!mUserManagerInternal.exists(userId)) {
+ if (userId != UserHandle.USER_ALL && !mUserManagerInternal.exists(userId)) {
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return;
}
- userId = handleIncomingUser(userId, "addOnRoleHoldersChangedListenerAsUser");
+ userId = handleIncomingUser(userId, "addOnRoleHoldersChangedListenerAsUser",
+ true);
getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS,
"addOnRoleHoldersChangedListenerAsUser");
@@ -485,11 +497,12 @@
public void removeOnRoleHoldersChangedListenerAsUser(
@NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
Preconditions.checkNotNull(listener, "listener cannot be null");
- if (!mUserManagerInternal.exists(userId)) {
+ if (userId != UserHandle.USER_ALL && !mUserManagerInternal.exists(userId)) {
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return;
}
- userId = handleIncomingUser(userId, "removeOnRoleHoldersChangedListenerAsUser");
+ userId = handleIncomingUser(userId, "removeOnRoleHoldersChangedListenerAsUser",
+ true);
getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS,
"removeOnRoleHoldersChangedListenerAsUser");
@@ -553,9 +566,10 @@
}
@CheckResult
- private int handleIncomingUser(@UserIdInt int userId, @NonNull String name) {
+ private int handleIncomingUser(@UserIdInt int userId, @NonNull String name,
+ boolean allowAll) {
return ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
- false, true, name, null);
+ allowAll, true, name, null);
}
@Override
diff --git a/services/core/java/com/android/server/rollback/RollbackData.java b/services/core/java/com/android/server/rollback/RollbackData.java
index f0589aa..a4f3064 100644
--- a/services/core/java/com/android/server/rollback/RollbackData.java
+++ b/services/core/java/com/android/server/rollback/RollbackData.java
@@ -29,6 +29,11 @@
*/
class RollbackData {
/**
+ * A unique identifier for this rollback.
+ */
+ public final int rollbackId;
+
+ /**
* The per-package rollback information.
*/
public final List<PackageRollbackInfo> packages = new ArrayList<>();
@@ -44,7 +49,16 @@
*/
public Instant timestamp;
- RollbackData(File backupDir) {
+ /**
+ * Whether this Rollback is currently in progress. This field is true from the point
+ * we commit a {@code PackageInstaller} session containing these packages to the point the
+ * {@code PackageInstaller} calls into the {@code onFinished} callback.
+ */
+ // NOTE: All accesses to this field are from the RollbackManager handler thread.
+ public boolean inProgress = false;
+
+ RollbackData(int rollbackId, File backupDir) {
+ this.rollbackId = rollbackId;
this.backupDir = backupDir;
}
}
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index d12f7ed..693d5d6 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -16,7 +16,6 @@
package com.android.server.rollback;
-import android.Manifest;
import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -32,10 +31,10 @@
import android.content.pm.PackageParser;
import android.content.pm.ParceledListSlice;
import android.content.pm.StringParceledListSlice;
+import android.content.pm.VersionedPackage;
import android.content.rollback.IRollbackManager;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
-import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
@@ -43,24 +42,21 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.storage.StorageManager;
import android.util.Log;
+import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
+import com.android.server.pm.Installer;
+import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.PackageManagerServiceUtils;
-import libcore.io.IoUtils;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
import java.io.File;
import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.file.Files;
+import java.security.SecureRandom;
import java.time.Instant;
-import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
@@ -68,9 +64,9 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Random;
import java.util.Set;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
/**
* Implementation of service that manages APK level rollbacks.
@@ -88,6 +84,13 @@
// mLock is held when they are called.
private final Object mLock = new Object();
+ // Used for generating rollback IDs.
+ private final Random mRandom = new SecureRandom();
+
+ // Set of allocated rollback ids
+ @GuardedBy("mLock")
+ private final SparseBooleanArray mAllocatedRollbackIds = new SparseBooleanArray();
+
// Package rollback data for rollback-enabled installs that have not yet
// been committed. Maps from sessionId to rollback data.
@GuardedBy("mLock")
@@ -108,46 +111,22 @@
@GuardedBy("mLock")
private List<RollbackInfo> mRecentlyExecutedRollbacks;
- // Data for available rollbacks and recently executed rollbacks is
- // persisted in storage. Assuming the rollback data directory is
- // /data/rollback, we use the following directory structure
- // to store this data:
- // /data/rollback/
- // available/
- // XXX/
- // com.package.A/
- // base.apk
- // info.json
- // enabled.txt
- // YYY/
- // com.package.B/
- // base.apk
- // info.json
- // enabled.txt
- // recently_executed.json
- //
- // * XXX, YYY are random strings from Files.createTempDirectory
- // * info.json contains the package version to roll back from/to.
- // * enabled.txt contains a timestamp for when the rollback was first
- // made available. This file is not written until the rollback is made
- // available.
- //
- // TODO: Use AtomicFile for all the .json files?
- private final File mRollbackDataDir;
- private final File mAvailableRollbacksDir;
- private final File mRecentlyExecutedRollbacksFile;
+ private final RollbackStore mRollbackStore;
private final Context mContext;
private final HandlerThread mHandlerThread;
+ private final Installer mInstaller;
RollbackManagerServiceImpl(Context context) {
mContext = context;
+ // Note that we're calling onStart here because this object is only constructed on
+ // SystemService#onStart.
+ mInstaller = new Installer(mContext);
+ mInstaller.onStart();
mHandlerThread = new HandlerThread("RollbackManagerServiceHandler");
mHandlerThread.start();
- mRollbackDataDir = new File(Environment.getDataDirectory(), "rollback");
- mAvailableRollbacksDir = new File(mRollbackDataDir, "available");
- mRecentlyExecutedRollbacksFile = new File(mRollbackDataDir, "recently_executed.json");
+ mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback"));
// Kick off loading of the rollback data from strorage in a background
// thread.
@@ -158,8 +137,8 @@
// expiration.
getHandler().post(() -> ensureRollbackDataLoaded());
- PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
- installer.registerSessionCallback(new SessionCallback(), getHandler());
+ PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
+ packageInstaller.registerSessionCallback(new SessionCallback(), getHandler());
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
@@ -196,10 +175,13 @@
PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_TOKEN, -1);
int installFlags = intent.getIntExtra(
PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS, 0);
+ int[] installedUsers = intent.getIntArrayExtra(
+ PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS);
File newPackageCodePath = new File(intent.getData().getPath());
getHandler().post(() -> {
- boolean success = enableRollback(installFlags, newPackageCodePath);
+ boolean success = enableRollback(installFlags, newPackageCodePath,
+ installedUsers);
int ret = PackageManagerInternal.ENABLE_ROLLBACK_SUCCEEDED;
if (!success) {
ret = PackageManagerInternal.ENABLE_ROLLBACK_FAILED;
@@ -236,10 +218,10 @@
// it's out of date or not, so no need to check package versions here.
for (PackageRollbackInfo info : data.packages) {
- if (info.packageName.equals(packageName)) {
+ if (info.getPackageName().equals(packageName)) {
// TODO: Once the RollbackInfo API supports info about
// dependant packages, add that info here.
- return new RollbackInfo(info);
+ return new RollbackInfo(data.rollbackId, info);
}
}
return null;
@@ -257,7 +239,7 @@
for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
RollbackData data = mAvailableRollbacks.get(i);
for (PackageRollbackInfo info : data.packages) {
- packageNames.add(info.packageName);
+ packageNames.add(info.getPackageName());
}
}
}
@@ -299,25 +281,24 @@
*/
private void executeRollbackInternal(RollbackInfo rollback,
String callerPackageName, IntentSender statusReceiver) {
- String targetPackageName = rollback.targetPackage.packageName;
+ String targetPackageName = rollback.targetPackage.getPackageName();
Log.i(TAG, "Initiating rollback of " + targetPackageName);
// Get the latest RollbackData for the target package.
- RollbackData data = getRollbackForPackage(targetPackageName);
+ final RollbackData data = getRollbackForPackage(targetPackageName);
if (data == null) {
sendFailure(statusReceiver, "No rollback available for package.");
return;
}
- // Verify the latest rollback matches the version requested.
- // TODO: Check dependant packages too once RollbackInfo includes that
- // information.
- for (PackageRollbackInfo info : data.packages) {
- if (info.packageName.equals(targetPackageName)
- && !rollback.targetPackage.higherVersion.equals(info.higherVersion)) {
- sendFailure(statusReceiver, "Rollback is out of date.");
- return;
- }
+ if (data.rollbackId != rollback.getRollbackId()) {
+ sendFailure(statusReceiver, "Rollback for package is out of date.");
+ return;
+ }
+
+ if (data.inProgress) {
+ sendFailure(statusReceiver, "Rollback for package is already in progress.");
+ return;
}
// Verify the RollbackData is up to date with what's installed on
@@ -329,15 +310,14 @@
// Figure out how to ensure we don't commit the rollback if
// roll forward happens at the same time.
for (PackageRollbackInfo info : data.packages) {
- PackageRollbackInfo.PackageVersion installedVersion =
- getInstalledPackageVersion(info.packageName);
+ VersionedPackage installedVersion = getInstalledPackageVersion(info.getPackageName());
if (installedVersion == null) {
// TODO: Test this case
sendFailure(statusReceiver, "Package to roll back is not installed");
return;
}
- if (!info.higherVersion.equals(installedVersion)) {
+ if (!packageVersionsEqual(info.getVersionRolledBackFrom(), installedVersion)) {
// TODO: Test this case
sendFailure(statusReceiver, "Package version to roll back not installed.");
return;
@@ -357,8 +337,14 @@
PackageManager pm = context.getPackageManager();
try {
PackageInstaller packageInstaller = pm.getPackageInstaller();
+ String installerPackageName = pm.getInstallerPackageName(targetPackageName);
+ if (installerPackageName == null) {
+ sendFailure(statusReceiver, "Cannot find installer package");
+ return;
+ }
PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ parentParams.setInstallerPackageName(installerPackageName);
parentParams.setAllowDowngrade(true);
parentParams.setMultiPackage();
int parentSessionId = packageInstaller.createSession(parentParams);
@@ -367,13 +353,14 @@
for (PackageRollbackInfo info : data.packages) {
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.setInstallerPackageName(installerPackageName);
params.setAllowDowngrade(true);
int sessionId = packageInstaller.createSession(params);
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
// TODO: Will it always be called "base.apk"? What about splits?
// What about apex?
- File packageDir = new File(data.backupDir, info.packageName);
+ File packageDir = new File(data.backupDir, info.getPackageName());
File baseApk = new File(packageDir, "base.apk");
try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(baseApk,
ParcelFileDescriptor.MODE_READ_ONLY)) {
@@ -387,28 +374,35 @@
parentSession.addChildSessionId(sessionId);
}
- final LocalIntentReceiver receiver = new LocalIntentReceiver();
+ final LocalIntentReceiver receiver = new LocalIntentReceiver(
+ (Intent result) -> {
+ // We've now completed the rollback, so we mark it as no longer in
+ // progress.
+ data.inProgress = false;
+
+ int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ if (status != PackageInstaller.STATUS_SUCCESS) {
+ sendFailure(statusReceiver, "Rollback downgrade install failed: "
+ + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE));
+ return;
+ }
+
+ addRecentlyExecutedRollback(rollback);
+ sendSuccess(statusReceiver);
+
+ Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
+
+ // TODO: This call emits the warning "Calling a method in the
+ // system process without a qualified user". Fix that.
+ // TODO: Limit this to receivers holding the
+ // MANAGE_ROLLBACKS permission?
+ mContext.sendBroadcast(broadcast);
+ }
+ );
+
+ data.inProgress = true;
parentSession.commit(receiver.getIntentSender());
-
- Intent result = receiver.getResult();
- int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
- PackageInstaller.STATUS_FAILURE);
- if (status != PackageInstaller.STATUS_SUCCESS) {
- sendFailure(statusReceiver, "Rollback downgrade install failed: "
- + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE));
- return;
- }
-
- addRecentlyExecutedRollback(rollback);
- sendSuccess(statusReceiver);
-
- Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED,
- Uri.fromParts("package", targetPackageName,
- Manifest.permission.MANAGE_ROLLBACKS));
-
- // TODO: This call emits the warning "Calling a method in the
- // system process without a qualified user". Fix that.
- mContext.sendBroadcast(broadcast);
} catch (IOException e) {
Log.e(TAG, "Unable to roll back " + targetPackageName, e);
sendFailure(statusReceiver, "IOException: " + e.toString());
@@ -445,9 +439,9 @@
while (iter.hasNext()) {
RollbackData data = iter.next();
for (PackageRollbackInfo info : data.packages) {
- if (info.packageName.equals(packageName)) {
+ if (info.getPackageName().equals(packageName)) {
iter.remove();
- removeFile(data.backupDir);
+ mRollbackStore.deleteAvailableRollback(data);
break;
}
}
@@ -474,85 +468,26 @@
@GuardedBy("mLock")
private void ensureRollbackDataLoadedLocked() {
if (mAvailableRollbacks == null) {
- loadRollbackDataLocked();
+ loadAllRollbackDataLocked();
}
}
/**
- * Load rollback data from storage.
+ * Load all rollback data from storage.
* Note: We do potentially heavy IO here while holding mLock, because we
* have to have the rollback data loaded before we can do anything else
* meaningful.
*/
@GuardedBy("mLock")
- private void loadRollbackDataLocked() {
- mAvailableRollbacksDir.mkdirs();
- mAvailableRollbacks = new ArrayList<>();
- for (File rollbackDir : mAvailableRollbacksDir.listFiles()) {
- File enabledFile = new File(rollbackDir, "enabled.txt");
- // TODO: Delete any directories without an enabled.txt? That could
- // potentially delete pending rollback data if reloadPersistedData
- // is called, though there's no reason besides testing for that to
- // be called.
- if (rollbackDir.isDirectory() && enabledFile.isFile()) {
- RollbackData data = new RollbackData(rollbackDir);
- try {
- PackageRollbackInfo info = null;
- for (File packageDir : rollbackDir.listFiles()) {
- if (packageDir.isDirectory()) {
- File jsonFile = new File(packageDir, "info.json");
- String jsonString = IoUtils.readFileAsString(
- jsonFile.getAbsolutePath());
- JSONObject jsonObject = new JSONObject(jsonString);
- String packageName = jsonObject.getString("packageName");
- long higherVersionCode = jsonObject.getLong("higherVersionCode");
- long lowerVersionCode = jsonObject.getLong("lowerVersionCode");
-
- data.packages.add(new PackageRollbackInfo(packageName,
- new PackageRollbackInfo.PackageVersion(higherVersionCode),
- new PackageRollbackInfo.PackageVersion(lowerVersionCode)));
- }
- }
-
- if (data.packages.isEmpty()) {
- throw new IOException("No package rollback info found");
- }
-
- String enabledString = IoUtils.readFileAsString(enabledFile.getAbsolutePath());
- data.timestamp = Instant.parse(enabledString.trim());
- mAvailableRollbacks.add(data);
- } catch (IOException | JSONException | DateTimeParseException e) {
- Log.e(TAG, "Unable to read rollback data at " + rollbackDir, e);
- removeFile(rollbackDir);
- }
- }
+ private void loadAllRollbackDataLocked() {
+ mAvailableRollbacks = mRollbackStore.loadAvailableRollbacks();
+ for (RollbackData data : mAvailableRollbacks) {
+ mAllocatedRollbackIds.put(data.rollbackId, true);
}
- mRecentlyExecutedRollbacks = new ArrayList<>();
- if (mRecentlyExecutedRollbacksFile.exists()) {
- try {
- // TODO: How to cope with changes to the format of this file from
- // when RollbackStore is updated in the future?
- String jsonString = IoUtils.readFileAsString(
- mRecentlyExecutedRollbacksFile.getAbsolutePath());
- JSONObject object = new JSONObject(jsonString);
- JSONArray array = object.getJSONArray("recentlyExecuted");
- for (int i = 0; i < array.length(); ++i) {
- JSONObject element = array.getJSONObject(i);
- String packageName = element.getString("packageName");
- long higherVersionCode = element.getLong("higherVersionCode");
- long lowerVersionCode = element.getLong("lowerVersionCode");
- PackageRollbackInfo target = new PackageRollbackInfo(packageName,
- new PackageRollbackInfo.PackageVersion(higherVersionCode),
- new PackageRollbackInfo.PackageVersion(lowerVersionCode));
- RollbackInfo rollback = new RollbackInfo(target);
- mRecentlyExecutedRollbacks.add(rollback);
- }
- } catch (IOException | JSONException e) {
- // TODO: What to do here? Surely we shouldn't just forget about
- // everything after the point of exception?
- Log.e(TAG, "Failed to read recently executed rollbacks", e);
- }
+ mRecentlyExecutedRollbacks = mRollbackStore.loadRecentlyExecutedRollbacks();
+ for (RollbackInfo info : mRecentlyExecutedRollbacks) {
+ mAllocatedRollbackIds.put(info.getRollbackId(), true);
}
scheduleExpiration(0);
@@ -566,8 +501,7 @@
private void onPackageReplaced(String packageName) {
// TODO: Could this end up incorrectly deleting a rollback for a
// package that is about to be installed?
- PackageRollbackInfo.PackageVersion installedVersion =
- getInstalledPackageVersion(packageName);
+ VersionedPackage installedVersion = getInstalledPackageVersion(packageName);
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
@@ -575,10 +509,12 @@
while (iter.hasNext()) {
RollbackData data = iter.next();
for (PackageRollbackInfo info : data.packages) {
- if (info.packageName.equals(packageName)
- && !info.higherVersion.equals(installedVersion)) {
+ if (info.getPackageName().equals(packageName)
+ && !packageVersionsEqual(
+ info.getVersionRolledBackFrom(),
+ installedVersion)) {
iter.remove();
- removeFile(data.backupDir);
+ mRollbackStore.deleteAvailableRollback(data);
break;
}
}
@@ -599,49 +535,19 @@
boolean changed = false;
while (iter.hasNext()) {
RollbackInfo rollback = iter.next();
- if (packageName.equals(rollback.targetPackage.packageName)) {
+ if (packageName.equals(rollback.targetPackage.getPackageName())) {
iter.remove();
changed = true;
}
}
if (changed) {
- saveRecentlyExecutedRollbacksLocked();
+ mRollbackStore.saveRecentlyExecutedRollbacks(mRecentlyExecutedRollbacks);
}
}
}
/**
- * Write the list of recently executed rollbacks to storage.
- * Note: This happens while mLock is held, which should be okay because we
- * expect executed rollbacks to be modified only in exceptional cases.
- */
- @GuardedBy("mLock")
- private void saveRecentlyExecutedRollbacksLocked() {
- try {
- JSONObject json = new JSONObject();
- JSONArray array = new JSONArray();
- json.put("recentlyExecuted", array);
-
- for (int i = 0; i < mRecentlyExecutedRollbacks.size(); ++i) {
- RollbackInfo rollback = mRecentlyExecutedRollbacks.get(i);
- JSONObject element = new JSONObject();
- element.put("packageName", rollback.targetPackage.packageName);
- element.put("higherVersionCode", rollback.targetPackage.higherVersion.versionCode);
- element.put("lowerVersionCode", rollback.targetPackage.lowerVersion.versionCode);
- array.put(element);
- }
-
- PrintWriter pw = new PrintWriter(mRecentlyExecutedRollbacksFile);
- pw.println(json.toString());
- pw.close();
- } catch (IOException | JSONException e) {
- // TODO: What to do here?
- Log.e(TAG, "Failed to save recently executed rollbacks", e);
- }
- }
-
- /**
* Records that the given package has been recently rolled back.
*/
private void addRecentlyExecutedRollback(RollbackInfo rollback) {
@@ -650,7 +556,7 @@
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
mRecentlyExecutedRollbacks.add(rollback);
- saveRecentlyExecutedRollbacksLocked();
+ mRollbackStore.saveRecentlyExecutedRollbacks(mRecentlyExecutedRollbacks);
}
}
@@ -701,7 +607,7 @@
RollbackData data = iter.next();
if (!now.isBefore(data.timestamp.plusMillis(ROLLBACK_LIFETIME_DURATION_MILLIS))) {
iter.remove();
- removeFile(data.backupDir);
+ mRollbackStore.deleteAvailableRollback(data);
} else if (oldest == null || oldest.isAfter(data.timestamp)) {
oldest = data.timestamp;
}
@@ -748,12 +654,13 @@
* staged for install with rollback enabled. Called before the package has
* been installed.
*
- * @param id the id of the enable rollback request
* @param installFlags information about what is being installed.
* @param newPackageCodePath path to the package about to be installed.
+ * @param installedUsers the set of users for which a given package is installed.
* @return true if enabling the rollback succeeds, false otherwise.
*/
- private boolean enableRollback(int installFlags, File newPackageCodePath) {
+ private boolean enableRollback(int installFlags, File newPackageCodePath,
+ int[] installedUsers) {
if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
Log.e(TAG, "Rollbacks not supported for instant app install");
return false;
@@ -803,8 +710,7 @@
return false;
}
- PackageRollbackInfo.PackageVersion newVersion =
- new PackageRollbackInfo.PackageVersion(newPackage.versionCode);
+ VersionedPackage newVersion = new VersionedPackage(packageName, newPackage.versionCode);
// Get information about the currently installed package.
PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
@@ -815,11 +721,29 @@
Log.e(TAG, packageName + " is not installed");
return false;
}
- PackageRollbackInfo.PackageVersion installedVersion =
- new PackageRollbackInfo.PackageVersion(installedPackage.getLongVersionCode());
+ VersionedPackage installedVersion = new VersionedPackage(packageName,
+ installedPackage.getLongVersionCode());
- PackageRollbackInfo info = new PackageRollbackInfo(
- packageName, newVersion, installedVersion);
+ for (int user : installedUsers) {
+ final int storageFlags;
+ if (StorageManager.isFileEncryptedNativeOrEmulated()
+ && !StorageManager.isUserKeyUnlocked(user)) {
+ // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy
+ // across app user data until the user unlocks their device.
+ Log.e(TAG, "User: " + user + " isn't unlocked, skipping CE userdata backup.");
+ storageFlags = Installer.FLAG_STORAGE_DE;
+ } else {
+ storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
+ }
+
+ try {
+ mInstaller.snapshotAppData(packageName, user, storageFlags);
+ } catch (InstallerException ie) {
+ Log.e(TAG, "Unable to create app data snapshot for: " + packageName, ie);
+ }
+ }
+
+ PackageRollbackInfo info = new PackageRollbackInfo(newVersion, installedVersion);
RollbackData data;
try {
@@ -827,9 +751,8 @@
mChildSessions.put(childSessionId, parentSessionId);
data = mPendingRollbacks.get(parentSessionId);
if (data == null) {
- File backupDir = Files.createTempDirectory(
- mAvailableRollbacksDir.toPath(), null).toFile();
- data = new RollbackData(backupDir);
+ int rollbackId = allocateRollbackIdLocked();
+ data = mRollbackStore.createAvailableRollback(rollbackId);
mPendingRollbacks.put(parentSessionId, data);
}
data.packages.add(info);
@@ -839,85 +762,89 @@
return false;
}
- File packageDir = new File(data.backupDir, packageName);
+ File packageDir = mRollbackStore.packageCodePathForAvailableRollback(data, packageName);
packageDir.mkdirs();
- try {
- JSONObject json = new JSONObject();
- json.put("packageName", packageName);
- json.put("higherVersionCode", newVersion.versionCode);
- json.put("lowerVersionCode", installedVersion.versionCode);
-
- File jsonFile = new File(packageDir, "info.json");
- PrintWriter pw = new PrintWriter(jsonFile);
- pw.println(json.toString());
- pw.close();
- } catch (IOException | JSONException e) {
- Log.e(TAG, "Unable to create rollback for " + packageName, e);
- removeFile(packageDir);
- return false;
- }
// TODO: Copy by hard link instead to save on cpu and storage space?
int status = PackageManagerServiceUtils.copyPackage(installedPackage.codePath, packageDir);
if (status != PackageManager.INSTALL_SUCCEEDED) {
Log.e(TAG, "Unable to copy package for rollback for " + packageName);
- removeFile(packageDir);
return false;
}
return true;
}
- // TODO: Don't copy this from PackageManagerShellCommand like this?
- private static class LocalIntentReceiver {
- private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
+ @Override
+ public void restoreUserData(String packageName, int userId, int appId, long ceDataInode,
+ String seInfo, int token) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("restoureUserData may only be called by the system.");
+ }
+
+ getHandler().post(() -> {
+ PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+ final RollbackData rollbackData = getRollbackForPackage(packageName);
+ if (rollbackData == null) {
+ pmi.finishPackageInstall(token, false);
+ return;
+ }
+
+ if (!rollbackData.inProgress) {
+ Log.e(TAG, "Request to restore userData for: " + packageName
+ + ", but no rollback in progress.");
+ pmi.finishPackageInstall(token, false);
+ return;
+ }
+
+ final int storageFlags;
+ if (StorageManager.isFileEncryptedNativeOrEmulated()
+ && !StorageManager.isUserKeyUnlocked(userId)) {
+ // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy
+ // across app user data until the user unlocks their device.
+ Log.e(TAG, "User: " + userId + " isn't unlocked, skipping CE userdata restore.");
+
+ storageFlags = Installer.FLAG_STORAGE_DE;
+ } else {
+ storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
+ }
+
+ try {
+ mInstaller.restoreAppDataSnapshot(packageName, appId, ceDataInode,
+ seInfo, userId, storageFlags);
+ } catch (InstallerException ie) {
+ Log.e(TAG, "Unable to restore app data snapshot: " + packageName, ie);
+ }
+
+ pmi.finishPackageInstall(token, false);
+ });
+ }
+
+ private class LocalIntentReceiver {
+ final Consumer<Intent> mConsumer;
+
+ LocalIntentReceiver(Consumer<Intent> consumer) {
+ mConsumer = consumer;
+ }
private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
@Override
public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
- try {
- mResult.offer(intent, 5, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
+ getHandler().post(() -> mConsumer.accept(intent));
}
};
public IntentSender getIntentSender() {
return new IntentSender((IIntentSender) mLocalSender);
}
-
- public Intent getResult() {
- try {
- return mResult.take();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- /**
- * Deletes a file completely.
- * If the file is a directory, its contents are deleted as well.
- * Has no effect if the directory does not exist.
- */
- private void removeFile(File file) {
- if (file.isDirectory()) {
- for (File child : file.listFiles()) {
- removeFile(child);
- }
- }
- if (file.exists()) {
- file.delete();
- }
}
/**
* Gets the version of the package currently installed.
* Returns null if the package is not currently installed.
*/
- private PackageRollbackInfo.PackageVersion getInstalledPackageVersion(String packageName) {
+ private VersionedPackage getInstalledPackageVersion(String packageName) {
PackageManager pm = mContext.getPackageManager();
PackageInfo pkgInfo = null;
try {
@@ -926,7 +853,12 @@
return null;
}
- return new PackageRollbackInfo.PackageVersion(pkgInfo.getLongVersionCode());
+ return new VersionedPackage(packageName, pkgInfo.getLongVersionCode());
+ }
+
+ private boolean packageVersionsEqual(VersionedPackage a, VersionedPackage b) {
+ return a.getPackageName().equals(b.getPackageName())
+ && a.getLongVersionCode() == b.getLongVersionCode();
}
private class SessionCallback extends PackageInstaller.SessionCallback {
@@ -958,11 +890,8 @@
if (success) {
try {
data.timestamp = Instant.now();
- File enabledFile = new File(data.backupDir, "enabled.txt");
- PrintWriter pw = new PrintWriter(enabledFile);
- pw.println(data.timestamp.toString());
- pw.close();
+ mRollbackStore.saveAvailableRollback(data);
synchronized (mLock) {
// Note: There is a small window of time between when
// the session has been committed by the package
@@ -981,12 +910,12 @@
scheduleExpiration(ROLLBACK_LIFETIME_DURATION_MILLIS);
} catch (IOException e) {
Log.e(TAG, "Unable to enable rollback", e);
- removeFile(data.backupDir);
+ mRollbackStore.deleteAvailableRollback(data);
}
} else {
// The install session was aborted, clean up the pending
// install.
- removeFile(data.backupDir);
+ mRollbackStore.deleteAvailableRollback(data);
}
}
}
@@ -1005,7 +934,7 @@
for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
RollbackData data = mAvailableRollbacks.get(i);
for (PackageRollbackInfo info : data.packages) {
- if (info.packageName.equals(packageName)) {
+ if (info.getPackageName().equals(packageName)) {
return data;
}
}
@@ -1013,4 +942,19 @@
}
return null;
}
+
+ @GuardedBy("mLock")
+ private int allocateRollbackIdLocked() throws IOException {
+ int n = 0;
+ int rollbackId;
+ do {
+ rollbackId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
+ if (!mAllocatedRollbackIds.get(rollbackId, false)) {
+ mAllocatedRollbackIds.put(rollbackId, true);
+ return rollbackId;
+ }
+ } while (n++ < 32);
+
+ throw new IOException("Failed to allocate rollback ID");
+ }
}
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
new file mode 100644
index 0000000..7738be9
--- /dev/null
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.rollback;
+
+import android.content.pm.VersionedPackage;
+import android.content.rollback.PackageRollbackInfo;
+import android.content.rollback.RollbackInfo;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class for loading and saving rollback data to persistent storage.
+ */
+class RollbackStore {
+ private static final String TAG = "RollbackManager";
+
+ // Assuming the rollback data directory is /data/rollback, we use the
+ // following directory structure to store persisted data for available and
+ // recently executed rollbacks:
+ // /data/rollback/
+ // available/
+ // XXX/
+ // rollback.json
+ // com.package.A/
+ // base.apk
+ // com.package.B/
+ // base.apk
+ // YYY/
+ // rollback.json
+ // com.package.C/
+ // base.apk
+ // recently_executed.json
+ //
+ // * XXX, YYY are the rollbackIds for the corresponding rollbacks.
+ // * rollback.json contains all relevant metadata for the rollback. This
+ // file is not written until the rollback is made available.
+ //
+ // TODO: Use AtomicFile for all the .json files?
+ private final File mRollbackDataDir;
+ private final File mAvailableRollbacksDir;
+ private final File mRecentlyExecutedRollbacksFile;
+
+ RollbackStore(File rollbackDataDir) {
+ mRollbackDataDir = rollbackDataDir;
+ mAvailableRollbacksDir = new File(mRollbackDataDir, "available");
+ mRecentlyExecutedRollbacksFile = new File(mRollbackDataDir, "recently_executed.json");
+ }
+
+ /**
+ * Reads the list of available rollbacks from persistent storage.
+ */
+ List<RollbackData> loadAvailableRollbacks() {
+ List<RollbackData> availableRollbacks = new ArrayList<>();
+ mAvailableRollbacksDir.mkdirs();
+ for (File rollbackDir : mAvailableRollbacksDir.listFiles()) {
+ if (rollbackDir.isDirectory()) {
+ try {
+ RollbackData data = loadRollbackData(rollbackDir);
+ availableRollbacks.add(data);
+ } catch (IOException e) {
+ // Note: Deleting the rollbackDir here will cause pending
+ // rollbacks to be deleted. This should only ever happen
+ // if reloadPersistedData is called while there are
+ // pending rollbacks. The reloadPersistedData method is
+ // currently only for testing, so that should be okay.
+ Log.e(TAG, "Unable to read rollback data at " + rollbackDir, e);
+ removeFile(rollbackDir);
+ }
+ }
+ }
+ return availableRollbacks;
+ }
+
+ /**
+ * Reads the list of recently executed rollbacks from persistent storage.
+ */
+ List<RollbackInfo> loadRecentlyExecutedRollbacks() {
+ List<RollbackInfo> recentlyExecutedRollbacks = new ArrayList<>();
+ if (mRecentlyExecutedRollbacksFile.exists()) {
+ try {
+ // TODO: How to cope with changes to the format of this file from
+ // when RollbackStore is updated in the future?
+ String jsonString = IoUtils.readFileAsString(
+ mRecentlyExecutedRollbacksFile.getAbsolutePath());
+ JSONObject object = new JSONObject(jsonString);
+ JSONArray array = object.getJSONArray("recentlyExecuted");
+ for (int i = 0; i < array.length(); ++i) {
+ JSONObject element = array.getJSONObject(i);
+ int rollbackId = element.getInt("rollbackId");
+ String packageName = element.getString("packageName");
+ long higherVersionCode = element.getLong("higherVersionCode");
+ long lowerVersionCode = element.getLong("lowerVersionCode");
+ PackageRollbackInfo target = new PackageRollbackInfo(
+ new VersionedPackage(packageName, higherVersionCode),
+ new VersionedPackage(packageName, lowerVersionCode));
+ RollbackInfo rollback = new RollbackInfo(rollbackId, target);
+ recentlyExecutedRollbacks.add(rollback);
+ }
+ } catch (IOException | JSONException e) {
+ // TODO: What to do here? Surely we shouldn't just forget about
+ // everything after the point of exception?
+ Log.e(TAG, "Failed to read recently executed rollbacks", e);
+ }
+ }
+
+ return recentlyExecutedRollbacks;
+ }
+
+ /**
+ * Creates a new RollbackData instance with backupDir assigned.
+ */
+ RollbackData createAvailableRollback(int rollbackId) throws IOException {
+ File backupDir = new File(mAvailableRollbacksDir, Integer.toString(rollbackId));
+ return new RollbackData(rollbackId, backupDir);
+ }
+
+ /**
+ * Returns the directory where the code for a package should be stored for
+ * given rollback <code>data</code> and <code>packageName</code>.
+ */
+ File packageCodePathForAvailableRollback(RollbackData data, String packageName) {
+ return new File(data.backupDir, packageName);
+ }
+
+ /**
+ * Writes the metadata for an available rollback to persistent storage.
+ */
+ void saveAvailableRollback(RollbackData data) throws IOException {
+ try {
+ JSONObject dataJson = new JSONObject();
+ JSONArray packagesJson = new JSONArray();
+ for (PackageRollbackInfo info : data.packages) {
+ JSONObject infoJson = new JSONObject();
+ infoJson.put("packageName", info.getPackageName());
+ infoJson.put("higherVersionCode",
+ info.getVersionRolledBackFrom().getLongVersionCode());
+ infoJson.put("lowerVersionCode",
+ info.getVersionRolledBackTo().getVersionCode());
+ packagesJson.put(infoJson);
+ }
+ dataJson.put("rollbackId", data.rollbackId);
+ dataJson.put("packages", packagesJson);
+ dataJson.put("timestamp", data.timestamp.toString());
+
+ PrintWriter pw = new PrintWriter(new File(data.backupDir, "rollback.json"));
+ pw.println(dataJson.toString());
+ pw.close();
+ } catch (JSONException e) {
+ throw new IOException(e);
+ }
+ }
+
+ /**
+ * Removes all persistant storage associated with the given available
+ * rollback.
+ */
+ void deleteAvailableRollback(RollbackData data) {
+ // TODO(narayan): Make sure we delete the userdata snapshot along with the backup of the
+ // actual app.
+ removeFile(data.backupDir);
+ }
+
+ /**
+ * Writes the list of recently executed rollbacks to storage.
+ */
+ void saveRecentlyExecutedRollbacks(List<RollbackInfo> recentlyExecutedRollbacks) {
+ try {
+ JSONObject json = new JSONObject();
+ JSONArray array = new JSONArray();
+ json.put("recentlyExecuted", array);
+
+ for (int i = 0; i < recentlyExecutedRollbacks.size(); ++i) {
+ RollbackInfo rollback = recentlyExecutedRollbacks.get(i);
+ JSONObject element = new JSONObject();
+ element.put("rollbackId", rollback.getRollbackId());
+ element.put("packageName", rollback.targetPackage.getPackageName());
+ element.put("higherVersionCode",
+ rollback.targetPackage.getVersionRolledBackFrom().getLongVersionCode());
+ element.put("lowerVersionCode",
+ rollback.targetPackage.getVersionRolledBackTo().getLongVersionCode());
+ array.put(element);
+ }
+
+ PrintWriter pw = new PrintWriter(mRecentlyExecutedRollbacksFile);
+ pw.println(json.toString());
+ pw.close();
+ } catch (IOException | JSONException e) {
+ // TODO: What to do here?
+ Log.e(TAG, "Failed to save recently executed rollbacks", e);
+ }
+ }
+
+ /**
+ * Reads the metadata for a rollback from the given directory.
+ * @throws IOException in case of error reading the data.
+ */
+ private RollbackData loadRollbackData(File backupDir) throws IOException {
+ try {
+ File rollbackJsonFile = new File(backupDir, "rollback.json");
+ JSONObject dataJson = new JSONObject(
+ IoUtils.readFileAsString(rollbackJsonFile.getAbsolutePath()));
+
+ int rollbackId = dataJson.getInt("rollbackId");
+ RollbackData data = new RollbackData(rollbackId, backupDir);
+
+ JSONArray packagesJson = dataJson.getJSONArray("packages");
+ for (int i = 0; i < packagesJson.length(); ++i) {
+ JSONObject infoJson = packagesJson.getJSONObject(i);
+ String packageName = infoJson.getString("packageName");
+ long higherVersionCode = infoJson.getLong("higherVersionCode");
+ long lowerVersionCode = infoJson.getLong("lowerVersionCode");
+ data.packages.add(new PackageRollbackInfo(
+ new VersionedPackage(packageName, higherVersionCode),
+ new VersionedPackage(packageName, lowerVersionCode)));
+ }
+
+ data.timestamp = Instant.parse(dataJson.getString("timestamp"));
+ return data;
+ } catch (JSONException | DateTimeParseException e) {
+ throw new IOException(e);
+ }
+ }
+
+ /**
+ * Deletes a file completely.
+ * If the file is a directory, its contents are deleted as well.
+ * Has no effect if the directory does not exist.
+ */
+ private void removeFile(File file) {
+ if (file.isDirectory()) {
+ for (File child : file.listFiles()) {
+ removeFile(child);
+ }
+ }
+ if (file.exists()) {
+ file.delete();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
index 56db32a..146c516 100644
--- a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
+++ b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
@@ -41,8 +41,8 @@
private static final boolean DBG = false;
private static final String DEBUG_KEY =
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60"
- + "pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==";
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13YrU1haIhVC5296InRu1"
+ + "x7A8PV1ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg==";
private static final String PROD_KEY =
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+lky6wKyGL6lE1VrD0YTMHwb0Xwc+tzC8MvnrzVxodvTp"
+ "VY/jV7V+Zktcx+pry43XPABFRXtbhTo+qykhyBA1g==";
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 4e71a05..acede7d 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -96,10 +96,10 @@
import com.android.internal.os.BinderCallsStats.ExportedCallStat;
import com.android.internal.os.KernelCpuSpeedReader;
import com.android.internal.os.KernelCpuThreadReader;
-import com.android.internal.os.KernelUidCpuActiveTimeReader;
-import com.android.internal.os.KernelUidCpuClusterTimeReader;
-import com.android.internal.os.KernelUidCpuFreqTimeReader;
-import com.android.internal.os.KernelUidCpuTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
import com.android.internal.os.KernelWakelockReader;
import com.android.internal.os.KernelWakelockStats;
import com.android.internal.os.LooperStats;
@@ -231,14 +231,16 @@
private final HashMap<Long, String> mDeletedFiles = new HashMap<>();
private final CompanionHandler mHandler;
- private KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
+ // Disables throttler on CPU time readers.
+ private KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader =
+ new KernelCpuUidUserSysTimeReader(false);
private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
- private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
- new KernelUidCpuFreqTimeReader();
- private KernelUidCpuActiveTimeReader mKernelUidCpuActiveTimeReader =
- new KernelUidCpuActiveTimeReader();
- private KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader =
- new KernelUidCpuClusterTimeReader();
+ private KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader =
+ new KernelCpuUidFreqTimeReader(false);
+ private KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader =
+ new KernelCpuUidActiveTimeReader(false);
+ private KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader =
+ new KernelCpuUidClusterTimeReader(false);
private StoragedUidIoStatsReader mStoragedUidIoStatsReader =
new StoragedUidIoStatsReader();
@Nullable
@@ -294,12 +296,6 @@
numSpeedSteps);
firstCpuOfCluster += powerProfile.getNumCoresInCpuCluster(i);
}
- // use default throttling in
- // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
- mKernelUidCpuFreqTimeReader.setThrottleInterval(0);
- long[] freqs = mKernelUidCpuFreqTimeReader.readFreqs(powerProfile);
- mKernelUidCpuClusterTimeReader.setThrottleInterval(0);
- mKernelUidCpuActiveTimeReader.setThrottleInterval(0);
// Enable push notifications of throttling from vendor thermal
// management subsystem via thermalservice.
@@ -914,7 +910,8 @@
private void pullKernelUidCpuTime(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
- mKernelUidCpuTimeReader.readAbsolute((uid, userTimeUs, systemTimeUs) -> {
+ mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> {
+ long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
e.writeInt(uid);
e.writeLong(userTimeUs);
@@ -926,7 +923,7 @@
private void pullKernelUidCpuFreqTime(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
- mKernelUidCpuFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
+ mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
if (cpuFreqTimeMs[freqIndex] != 0) {
StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
@@ -943,7 +940,7 @@
private void pullKernelUidCpuClusterTime(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
- mKernelUidCpuClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
+ mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
for (int i = 0; i < cpuClusterTimesMs.length; i++) {
StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
wallClockNanos);
@@ -958,7 +955,7 @@
private void pullKernelUidCpuActiveTime(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
- mKernelUidCpuActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
+ mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
e.writeInt(uid);
e.writeLong((long) cpuActiveTimesMs);
@@ -1708,6 +1705,27 @@
}
}
+ private void pullTemperature(int tagId, long elapsedNanos, long wallClockNanos,
+ List<StatsLogEventWrapper> pulledData) {
+ long callingToken = Binder.clearCallingIdentity();
+ try {
+ List<Temperature> temperatures = sThermalService.getCurrentTemperatures();
+ for (Temperature temp : temperatures) {
+ StatsLogEventWrapper e =
+ new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ e.writeInt(temp.getType());
+ e.writeString(temp.getName());
+ e.writeInt((int) (temp.getValue() * 10));
+ pulledData.add(e);
+ }
+ } catch (RemoteException e) {
+ // Should not happen.
+ Slog.e(TAG, "Disconnected from thermal service. Cannot pull temperatures.");
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
+ }
+
/**
* Pulls various data.
*/
@@ -1870,6 +1888,10 @@
pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+ case StatsLog.TEMPERATURE: {
+ pullTemperature(tagId, elapsedNanos, wallClockNanos, ret);
+ break;
+ }
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 7c1e619..8d2bab4 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -1219,13 +1219,13 @@
}
@Override
- public void onNotificationExpansionChanged(String key, boolean userAction,
- boolean expanded) throws RemoteException {
+ public void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded,
+ int location) throws RemoteException {
enforceStatusBarService();
long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onNotificationExpansionChanged(
- key, userAction, expanded);
+ key, userAction, expanded, location);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
new file mode 100644
index 0000000..23c042a5
--- /dev/null
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -0,0 +1,328 @@
+/*
+ * 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.testharness;
+
+import android.annotation.Nullable;
+import android.app.KeyguardManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.BatteryManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.SystemService;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Set;
+
+/**
+ * Manages the Test Harness Mode service for setting up test harness mode on the device.
+ *
+ * <p>Test Harness Mode is a feature that allows the user to clean their device, retain ADB keys,
+ * and provision the device for Instrumentation testing. This means that all parts of the device
+ * that would otherwise interfere with testing (auto-syncing accounts, package verification,
+ * automatic updates, etc.) are all disabled by default but may be re-enabled by the user.
+ */
+public class TestHarnessModeService extends SystemService {
+ private static final String TAG = TestHarnessModeService.class.getSimpleName();
+ private static final String TEST_HARNESS_MODE_PROPERTY = "persist.sys.test_harness";
+
+ private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal;
+
+ public TestHarnessModeService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService("testharness", mService);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ switch (phase) {
+ case PHASE_SYSTEM_SERVICES_READY:
+ setUpTestHarnessMode();
+ break;
+ case PHASE_BOOT_COMPLETED:
+ disableAutoSync();
+ break;
+ }
+ super.onBootPhase(phase);
+ }
+
+ private void setUpTestHarnessMode() {
+ Slog.d(TAG, "Setting up test harness mode");
+ byte[] testHarnessModeData = getPersistentDataBlock().getTestHarnessModeData();
+ if (testHarnessModeData == null || testHarnessModeData.length == 0) {
+ // There's no data to apply, so leave it as-is.
+ return;
+ }
+ PersistentData persistentData = PersistentData.fromBytes(testHarnessModeData);
+
+ SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, persistentData.mEnabled ? "1" : "0");
+ writeAdbKeysFile(persistentData);
+ // Clear out the data block so that we don't revert the ADB keys on every boot.
+ getPersistentDataBlock().clearTestHarnessModeData();
+
+ ContentResolver cr = getContext().getContentResolver();
+ if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 0) {
+ // Enable ADB
+ Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 1);
+ } else {
+ // ADB is already enabled, we should restart the service so it picks up the new keys
+ android.os.SystemService.restart("adbd");
+ }
+
+ Settings.Global.putInt(cr, Settings.Global.PACKAGE_VERIFIER_ENABLE, 0);
+ Settings.Global.putInt(
+ cr,
+ Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
+ BatteryManager.BATTERY_PLUGGED_ANY);
+ Settings.Global.putInt(cr, Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE, 1);
+ Settings.Global.putInt(cr, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+
+ setDeviceProvisioned();
+ }
+
+ private void disableAutoSync() {
+ UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser();
+ ContentResolver
+ .setMasterSyncAutomaticallyAsUser(false, primaryUser.getUserHandle().getIdentifier());
+ }
+
+ private void writeAdbKeysFile(PersistentData persistentData) {
+ Path adbKeys = Paths.get("/data/misc/adb/adb_keys");
+ try {
+ OutputStream fileOutputStream = Files.newOutputStream(adbKeys);
+ fileOutputStream.write(persistentData.mAdbKeys);
+ fileOutputStream.close();
+
+ Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(adbKeys);
+ permissions.add(PosixFilePermission.GROUP_READ);
+ Files.setPosixFilePermissions(adbKeys, permissions);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to set up adb keys", e);
+ // Note: if a device enters this block, it will remain UNAUTHORIZED in ADB, but all
+ // other settings will be set up.
+ }
+ }
+
+ // Setting the device as provisioned skips the setup wizard.
+ private void setDeviceProvisioned() {
+ ContentResolver cr = getContext().getContentResolver();
+ Settings.Global.putInt(cr, Settings.Global.DEVICE_PROVISIONED, 1);
+ Settings.Secure.putIntForUser(
+ cr,
+ Settings.Secure.USER_SETUP_COMPLETE,
+ 1,
+ UserHandle.USER_CURRENT);
+ }
+
+ @Nullable
+ private PersistentDataBlockManagerInternal getPersistentDataBlock() {
+ if (mPersistentDataBlockManagerInternal == null) {
+ Slog.d(TAG, "Getting PersistentDataBlockManagerInternal from LocalServices");
+ mPersistentDataBlockManagerInternal =
+ LocalServices.getService(PersistentDataBlockManagerInternal.class);
+ }
+ return mPersistentDataBlockManagerInternal;
+ }
+
+ private final IBinder mService = new Binder() {
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ (new TestHarnessModeShellCommand())
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+ };
+
+ private class TestHarnessModeShellCommand extends ShellCommand {
+ @Override
+ public int onCommand(String cmd) {
+ switch (cmd) {
+ case "enable":
+ case "restore":
+ checkPermissions();
+ final long originalId = Binder.clearCallingIdentity();
+ try {
+ if (isDeviceSecure()) {
+ getErrPrintWriter().println(
+ "Test Harness Mode cannot be enabled if there is a lock "
+ + "screen");
+ return 2;
+ }
+ return handleEnable();
+ } finally {
+ Binder.restoreCallingIdentity(originalId);
+ }
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ private void checkPermissions() {
+ getContext().enforceCallingPermission(
+ android.Manifest.permission.ENABLE_TEST_HARNESS_MODE,
+ "You must hold android.permission.ENABLE_TEST_HARNESS_MODE "
+ + "to enable Test Harness Mode");
+ }
+
+ private boolean isDeviceSecure() {
+ UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser();
+ KeyguardManager keyguardManager = getContext().getSystemService(KeyguardManager.class);
+ return keyguardManager.isDeviceSecure(primaryUser.id);
+ }
+
+ private int handleEnable() {
+ Path adbKeys = Paths.get("/data/misc/adb/adb_keys");
+ if (!Files.exists(adbKeys)) {
+ // This should only be accessible on eng builds that haven't yet set up ADB keys
+ getErrPrintWriter()
+ .println("No ADB keys stored; not enabling test harness mode");
+ return 1;
+ }
+
+ try (InputStream inputStream = Files.newInputStream(adbKeys)) {
+ long size = Files.size(adbKeys);
+ byte[] adbKeysBytes = new byte[(int) size];
+ int numBytes = inputStream.read(adbKeysBytes);
+ if (numBytes != size) {
+ getErrPrintWriter().println("Failed to read all bytes of adb_keys");
+ return 1;
+ }
+ PersistentData persistentData = new PersistentData(true, adbKeysBytes);
+ getPersistentDataBlock().setTestHarnessModeData(persistentData.toBytes());
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to store ADB keys.", e);
+ getErrPrintWriter().println("Failed to enable Test Harness Mode");
+ return 1;
+ }
+
+ Intent i = new Intent(Intent.ACTION_FACTORY_RESET);
+ i.setPackage("android");
+ i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ i.putExtra(Intent.EXTRA_REASON, TAG);
+ i.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true);
+ getContext().sendBroadcastAsUser(i, UserHandle.SYSTEM);
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("About:");
+ pw.println(" Test Harness Mode is a mode that the device can be placed in to prepare");
+ pw.println(" the device for running UI tests. The device is placed into this mode by");
+ pw.println(" first wiping all data from the device, preserving ADB keys.");
+ pw.println();
+ pw.println(" By default, the following settings are configured:");
+ pw.println(" * Package Verifier is disabled");
+ pw.println(" * Stay Awake While Charging is enabled");
+ pw.println(" * OTA Updates are disabled");
+ pw.println(" * Auto-Sync for accounts is disabled");
+ pw.println();
+ pw.println(" Other apps may configure themselves differently in Test Harness Mode by");
+ pw.println(" checking ActivityManager.isRunningInUserTestHarness()");
+ pw.println();
+ pw.println("Test Harness Mode commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println();
+ pw.println(" enable|restore");
+ pw.println(" Erase all data from this device and enable Test Harness Mode,");
+ pw.println(" preserving the stored ADB keys currently on the device and toggling");
+ pw.println(" settings in a way that are conducive to Instrumentation testing.");
+ }
+ }
+
+ /**
+ * The object that will serialize/deserialize the Test Harness Mode data to and from the
+ * persistent data block.
+ */
+ public static class PersistentData {
+ static final byte VERSION_1 = 1;
+
+ final int mVersion;
+ final boolean mEnabled;
+ final byte[] mAdbKeys;
+
+ PersistentData(boolean enabled, byte[] adbKeys) {
+ this(VERSION_1, enabled, adbKeys);
+ }
+
+ PersistentData(int version, boolean enabled, byte[] adbKeys) {
+ this.mVersion = version;
+ this.mEnabled = enabled;
+ this.mAdbKeys = adbKeys;
+ }
+
+ static PersistentData fromBytes(byte[] bytes) {
+ try {
+ DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes));
+ int version = is.readInt();
+ boolean enabled = is.readBoolean();
+ int adbKeysLength = is.readInt();
+ byte[] adbKeys = new byte[adbKeysLength];
+ is.readFully(adbKeys);
+ return new PersistentData(version, enabled, adbKeys);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ byte[] toBytes() {
+ try {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(os);
+ dos.writeInt(VERSION_1);
+ dos.writeBoolean(mEnabled);
+ dos.writeInt(mAdbKeys.length);
+ dos.write(mAdbKeys);
+ dos.close();
+ return os.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index ced5935..423ec4c 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -22,10 +22,8 @@
import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
import android.app.admin.DevicePolicyManager;
-import android.hardware.biometrics.BiometricSourceType;
import android.app.trust.ITrustListener;
import android.app.trust.ITrustManager;
-import android.app.UserSwitchObserver;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -41,6 +39,7 @@
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.BiometricSourceType;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -72,13 +71,15 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
+
/**
* Manages trust agents and trust listeners.
@@ -119,7 +120,7 @@
private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000;
private static final String TRUST_TIMEOUT_ALARM_TAG = "TrustManagerService.trustTimeoutForUser";
- private static final long TRUST_TIMEOUT_IN_MILLIS = 20 * 1000; //4 * 60 * 60 * 1000;
+ private static final long TRUST_TIMEOUT_IN_MILLIS = 4 * 60 * 60 * 1000;
private final ArraySet<AgentInfo> mActiveAgents = new ArraySet<>();
private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<>();
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 9a7e75e..744efab 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -1129,7 +1129,8 @@
* In this case, we grant a uri permission, even if the ContentProvider does not normally
* grant uri permissions.
*/
- boolean specialCrossUserGrant = UserHandle.getUserId(targetUid) != grantUri.sourceUserId
+ boolean specialCrossUserGrant = targetUid >= 0
+ && UserHandle.getUserId(targetUid) != grantUri.sourceUserId
&& checkHoldingPermissionsInternal(pm, pi, grantUri, callingUid,
modeFlags, false /*without considering the uid permissions*/);
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index b3eafa4..45689ce 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -65,7 +65,6 @@
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.SystemService;
-import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.utils.ManagedApplicationService;
import com.android.server.utils.ManagedApplicationService.BinderChecker;
import com.android.server.utils.ManagedApplicationService.LogEvent;
@@ -623,14 +622,6 @@
}
@Override
- public void setVrInputMethod(ComponentName componentName) {
- enforceCallerPermissionAnyOf(Manifest.permission.RESTRICTED_VR_ACCESS);
- InputMethodManagerInternal imm =
- LocalServices.getService(InputMethodManagerInternal.class);
- imm.startVrInputMethodNoCheck(componentName);
- }
-
- @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index caebf15..545b69b 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -49,6 +49,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.TypedValue;
+import android.view.Display;
import android.view.MagnificationSpec;
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
@@ -83,23 +84,36 @@
mService = service;
}
- private DisplayMagnifier mDisplayMagnifier;
+ private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
private WindowsForAccessibilityObserver mWindowsForAccessibilityObserver;
- public void setMagnificationCallbacksLocked(MagnificationCallbacks callbacks) {
+ public boolean setMagnificationCallbacksLocked(int displayId,
+ MagnificationCallbacks callbacks) {
+ boolean result = false;
if (callbacks != null) {
- if (mDisplayMagnifier != null) {
+ if (mDisplayMagnifiers.get(displayId) != null) {
throw new IllegalStateException("Magnification callbacks already set!");
}
- mDisplayMagnifier = new DisplayMagnifier(mService, callbacks);
+ final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
+ if (dc != null) {
+ final Display display = dc.getDisplay();
+ if (display != null && display.getType() != Display.TYPE_OVERLAY) {
+ mDisplayMagnifiers.put(displayId, new DisplayMagnifier(
+ mService, dc, display, callbacks));
+ result = true;
+ }
+ }
} else {
- if (mDisplayMagnifier == null) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier == null) {
throw new IllegalStateException("Magnification callbacks already cleared!");
}
- mDisplayMagnifier.destroyLocked();
- mDisplayMagnifier = null;
+ displayMagnifier.destroyLocked();
+ mDisplayMagnifiers.remove(displayId);
+ result = true;
}
+ return result;
}
public void setWindowsForAccessibilityCallback(WindowsForAccessibilityCallback callback) {
@@ -129,58 +143,72 @@
}
}
- public void setMagnificationSpecLocked(MagnificationSpec spec) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.setMagnificationSpecLocked(spec);
+ public void setMagnificationSpecLocked(int displayId, MagnificationSpec spec) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.setMagnificationSpecLocked(spec);
}
- if (mWindowsForAccessibilityObserver != null) {
+ // TODO: support multi-display for windows observer
+ if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
}
}
- public void getMagnificationRegionLocked(Region outMagnificationRegion) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.getMagnificationRegionLocked(outMagnificationRegion);
+ public void getMagnificationRegionLocked(int displayId, Region outMagnificationRegion) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.getMagnificationRegionLocked(outMagnificationRegion);
}
}
- public void onRectangleOnScreenRequestedLocked(Rect rectangle) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle);
+ public void onRectangleOnScreenRequestedLocked(int displayId, Rect rectangle) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.onRectangleOnScreenRequestedLocked(rectangle);
}
// Not relevant for the window observer.
}
- public void onWindowLayersChangedLocked() {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.onWindowLayersChangedLocked();
+ public void onWindowLayersChangedLocked(int displayId) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.onWindowLayersChangedLocked();
}
- if (mWindowsForAccessibilityObserver != null) {
+ // TODO: support multi-display for windows observer
+ if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
}
}
public void onRotationChangedLocked(DisplayContent displayContent) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.onRotationChangedLocked(displayContent);
+ final int displayId = displayContent.getDisplayId();
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.onRotationChangedLocked(displayContent);
}
- if (mWindowsForAccessibilityObserver != null) {
+ // TODO: support multi-display for windows observer
+ if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
}
}
public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.onAppWindowTransitionLocked(windowState, transition);
+ final int displayId = windowState.getDisplayId();
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.onAppWindowTransitionLocked(windowState, transition);
}
// Not relevant for the window observer.
}
public void onWindowTransitionLocked(WindowState windowState, int transition) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.onWindowTransitionLocked(windowState, transition);
+ final int displayId = windowState.getDisplayId();
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.onWindowTransitionLocked(windowState, transition);
}
- if (mWindowsForAccessibilityObserver != null) {
+ // TODO: support multi-display for windows observer
+ if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
}
}
@@ -197,7 +225,6 @@
}
}
-
public void onSomeWindowResizedOrMovedLocked() {
// Not relevant for the display magnifier.
@@ -207,29 +234,34 @@
}
/** NOTE: This has to be called within a surface transaction. */
- public void drawMagnifiedRegionBorderIfNeededLocked() {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
+ public void drawMagnifiedRegionBorderIfNeededLocked(int displayId) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
}
// Not relevant for the window observer.
}
public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
- if (mDisplayMagnifier != null) {
- return mDisplayMagnifier.getMagnificationSpecForWindowLocked(windowState);
+ final int displayId = windowState.getDisplayId();
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ return displayMagnifier.getMagnificationSpecForWindowLocked(windowState);
}
return null;
}
public boolean hasCallbacksLocked() {
- return (mDisplayMagnifier != null
+ // TODO: support multi-display for windows observer
+ return (mDisplayMagnifiers.size() > 0
|| mWindowsForAccessibilityObserver != null);
}
- public void setForceShowMagnifiableBoundsLocked(boolean show) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.setForceShowMagnifiableBoundsLocked(show);
- mDisplayMagnifier.showMagnificationBoundsIfNeeded();
+ public void setForceShowMagnifiableBoundsLocked(int displayId, boolean show) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.setForceShowMagnifiableBoundsLocked(show);
+ displayMagnifier.showMagnificationBoundsIfNeeded();
}
}
@@ -263,6 +295,8 @@
private final WindowManagerService mService;
private final MagnifiedViewport mMagnifedViewport;
private final Handler mHandler;
+ private final DisplayContent mDisplayContent;
+ private final Display mDisplay;
private final MagnificationCallbacks mCallbacks;
@@ -271,10 +305,14 @@
private boolean mForceShowMagnifiableBounds = false;
public DisplayMagnifier(WindowManagerService windowManagerService,
+ DisplayContent displayContent,
+ Display display,
MagnificationCallbacks callbacks) {
mContext = windowManagerService.mContext;
mService = windowManagerService;
mCallbacks = callbacks;
+ mDisplayContent = displayContent;
+ mDisplay = display;
mHandler = new MyHandler(mService.mH.getLooper());
mMagnifedViewport = new MagnifiedViewport();
mLongAnimationDuration = mContext.getResources().getInteger(
@@ -285,7 +323,7 @@
mMagnifedViewport.updateMagnificationSpecLocked(spec);
mMagnifedViewport.recomputeBoundsLocked();
- mService.applyMagnificationSpec(spec);
+ mService.applyMagnificationSpecLocked(mDisplay.getDisplayId(), spec);
mService.scheduleAnimationLocked();
}
@@ -482,7 +520,7 @@
if (mContext.getResources().getConfiguration().isScreenRound()) {
mCircularPath = new Path();
- mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+ mDisplay.getRealSize(mTempPoint);
final int centerXY = mTempPoint.x / 2;
mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW);
} else {
@@ -512,7 +550,7 @@
}
public void recomputeBoundsLocked() {
- mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+ mDisplay.getRealSize(mTempPoint);
final int screenWidth = mTempPoint.x;
final int screenHeight = mTempPoint.y;
@@ -671,9 +709,8 @@
}
private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
- final DisplayContent dc = mService.getDefaultDisplayContentLocked();
mTempLayer = 0;
- dc.forAllWindows((w) -> {
+ mDisplayContent.forAllWindows((w) -> {
if (w.isOnScreen() && w.isVisibleLw()
&& (w.mAttrs.alpha != 0)
&& !w.mWinAnimator.mEnterAnimationPending) {
@@ -703,8 +740,9 @@
public ViewportWindow(Context context) {
SurfaceControl surfaceControl = null;
try {
- mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
- surfaceControl = mService.getDefaultDisplayContentLocked().makeOverlay()
+ mDisplay.getRealSize(mTempPoint);
+ surfaceControl = mDisplayContent
+ .makeOverlay()
.setName(SURFACE_TITLE)
.setBufferSize(mTempPoint.x, mTempPoint.y) // not a typo
.setFormat(PixelFormat.TRANSLUCENT)
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index e817dd4..65d66f4 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -558,26 +558,22 @@
}
/**
- * Pause all activities in either all of the stacks or just the back stacks. This is done before
- * resuming a new activity and to make sure that previously active activities are
- * paused in stacks that are no longer visible or in pinned windowing mode. This does not
- * pause activities in visible stacks, so if an activity is launched within the same stack/task,
- * then we should explicitly pause that stack's top activity.
+ * Pause all activities in either all of the stacks or just the back stacks.
* @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
* @param resuming The resuming activity.
* @param dontWait The resuming activity isn't going to wait for all activities to be paused
* before resuming.
- * @return {@code true} if any activity was paused as a result of this call.
+ * @return true if any activity was paused as a result of this call.
*/
boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) {
boolean someActivityPaused = false;
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = mStacks.get(stackNdx);
- final ActivityRecord resumedActivity = stack.getResumedActivity();
- if (resumedActivity != null
- && (!stack.shouldBeVisible(resuming) || !stack.isFocusable())) {
+ // TODO(b/111541062): Check if resumed activity on this display instead
+ if (!mRootActivityContainer.isTopDisplayFocusedStack(stack)
+ && stack.getResumedActivity() != null) {
if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
- " mResumedActivity=" + resumedActivity);
+ " mResumedActivity=" + stack.getResumedActivity());
someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming,
dontWait);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6213fa0..b8634d8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1946,84 +1946,30 @@
try {
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
WindowVisibilityItem.obtain(true /* showWindow */));
- makeActiveIfNeeded(null /* activeActivity*/);
+ if (shouldPauseWhenBecomingVisible()) {
+ // An activity must be in the {@link PAUSING} state for the system to validate
+ // the move to {@link PAUSED}.
+ setState(PAUSING, "makeVisibleIfNeeded");
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ PauseActivityItem.obtain(finishing, false /* userLeaving */,
+ configChangeFlags, false /* dontReport */));
+ }
} catch (Exception e) {
Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e);
}
}
- /**
- * Make activity resumed or paused if needed.
- * @param activeActivity an activity that is resumed or just completed pause action.
- * We won't change the state of this activity.
- */
- boolean makeActiveIfNeeded(ActivityRecord activeActivity) {
- if (shouldResumeActivity(activeActivity)) {
- if (DEBUG_VISIBILITY) {
- Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this);
- }
- return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */,
- null /* options */);
- } else if (shouldPauseActivity(activeActivity)) {
- if (DEBUG_VISIBILITY) {
- Slog.v("TAG_VISIBILITY", "Pause visible activity, " + this);
- }
- // An activity must be in the {@link PAUSING} state for the system to validate
- // the move to {@link PAUSED}.
- setState(PAUSING, "makeVisibleIfNeeded");
- try {
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
- PauseActivityItem.obtain(finishing, false /* userLeaving */,
- configChangeFlags, false /* dontReport */));
- } catch (Exception e) {
- Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
- }
- }
- return false;
- }
-
- /**
- * Check if activity should be moved to PAUSED state. The activity:
- * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)})
- * - should be non-focusable
- * - should not be currently pausing or paused
- * @param activeActivity the activity that is active or just completed pause action. We won't
- * resume if this activity is active.
- */
- private boolean shouldPauseActivity(ActivityRecord activeActivity) {
- return shouldMakeActive(activeActivity) && !isFocusable() && !isState(PAUSING, PAUSED);
- }
-
- /**
- * Check if activity should be moved to RESUMED state. The activity:
- * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)})
- * - should be focusable
- * @param activeActivity the activity that is active or just completed pause action. We won't
- * resume if this activity is active.
- */
- private boolean shouldResumeActivity(ActivityRecord activeActivity) {
- return shouldMakeActive(activeActivity) && isFocusable() && !isState(RESUMED);
- }
-
- /**
- * Check if activity is eligible to be made active (resumed of paused). The activity:
- * - should be paused, stopped or stopping
- * - should not be the currently active one
- * - should be either the topmost in task, or right below the top activity that is finishing
- * If all of these conditions are not met at the same time, the activity cannot be made active.
- */
- private boolean shouldMakeActive(ActivityRecord activeActivity) {
- // If the activity is stopped, stopping, cycle to an active state. We avoid doing
+ /** Check if activity should be moved to PAUSED state when it becomes visible. */
+ private boolean shouldPauseWhenBecomingVisible() {
+ // If the activity is stopped or stopping, cycle to the paused state. We avoid doing
// this when there is an activity waiting to become translucent as the extra binder
// calls will lead to noticeable jank. A later call to
- // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to a proper
- // active state.
- if (!isState(RESUMED, PAUSED, STOPPED, STOPPING)
- || getActivityStack().mTranslucentActivityWaiting != null) {
- return false;
- }
-
- if (this == activeActivity) {
+ // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to the proper
+ // paused state. We also avoid doing this for the activity the stack supervisor
+ // considers the resumed activity, as normal means will bring the activity from STOPPED
+ // to RESUMED. Adding PAUSING in this scenario will lead to double lifecycles.
+ if (!isState(STOPPED, STOPPING) || getActivityStack().mTranslucentActivityWaiting != null
+ || isResumedActivityOnDisplay()) {
return false;
}
@@ -2033,14 +1979,14 @@
throw new IllegalStateException("Activity not found in its task");
}
if (positionInTask == task.mActivities.size() - 1) {
- // It's the topmost activity in the task - should become resumed now
+ // It's the topmost activity in the task - should become paused now
return true;
}
// Check if activity above is finishing now and this one becomes the topmost in task.
final ActivityRecord activityAbove = task.mActivities.get(positionInTask + 1);
if (activityAbove.finishing && results == null) {
- // We will only allow making active if activity above wasn't launched for result.
- // Otherwise it will cause this activity to resume before getting result.
+ // We will only allow pausing if activity above wasn't launched for result. Otherwise it
+ // will cause this activity to resume before getting result.
return true;
}
return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 3aef8e1f..891c3da 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -357,11 +357,6 @@
*/
boolean mForceHidden = false;
- /**
- * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively
- */
- boolean mInResumeTopActivity = false;
-
private boolean mUpdateBoundsDeferred;
private boolean mUpdateBoundsDeferredCalled;
private boolean mUpdateDisplayedBoundsDeferredCalled;
@@ -1737,7 +1732,6 @@
"Activity paused: token=" + token + ", timeout=" + timeout);
final ActivityRecord r = isInStackLocked(token);
-
if (r != null) {
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
if (mPausingActivity == r) {
@@ -2094,7 +2088,8 @@
boolean aboveTop = top != null;
final boolean stackShouldBeVisible = shouldBeVisible(starting);
boolean behindFullscreenActivity = !stackShouldBeVisible;
- boolean resumeNextActivity = isFocusable() && isInStackLocked(starting) == null;
+ boolean resumeNextActivity = mRootActivityContainer.isTopDisplayFocusedStack(this)
+ && (isInStackLocked(starting) == null);
final boolean isTopNotPinnedStack =
isAttached() && getDisplay().isTopNotPinnedStack(this);
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
@@ -2155,10 +2150,6 @@
if (r.handleAlreadyVisible()) {
resumeNextActivity = false;
}
-
- if (notifyClients) {
- r.makeActiveIfNeeded(starting);
- }
} else {
r.makeVisibleIfNeeded(starting, notifyClients);
}
@@ -2336,7 +2327,7 @@
r.setVisible(true);
}
if (r != starting) {
- mStackSupervisor.startSpecificActivityLocked(r, andResume, true /* checkConfig */);
+ mStackSupervisor.startSpecificActivityLocked(r, andResume, false);
return true;
}
}
@@ -2514,7 +2505,7 @@
*/
@GuardedBy("mService")
boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
- if (mInResumeTopActivity) {
+ if (mStackSupervisor.inResumeTopActivity) {
// Don't even start recursing.
return false;
}
@@ -2522,7 +2513,7 @@
boolean result = false;
try {
// Protect against recursion.
- mInResumeTopActivity = true;
+ mStackSupervisor.inResumeTopActivity = true;
result = resumeTopActivityInnerLocked(prev, options);
// When resuming the top activity, it may be necessary to pause the top activity (for
@@ -2537,7 +2528,7 @@
checkReadyForSleep();
}
} finally {
- mInResumeTopActivity = false;
+ mStackSupervisor.inResumeTopActivity = false;
}
return result;
@@ -2570,7 +2561,7 @@
// Find the next top-most activity to resume in this stack that is not finishing and is
// focusable. If it is not focusable, we will fall into the case below to resume the
// top activity in the next focusable task.
- ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
+ final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
final boolean hasRunningActivity = next != null;
@@ -2658,12 +2649,6 @@
if (!mRootActivityContainer.allPausedActivitiesComplete()) {
if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE,
"resumeTopActivityLocked: Skip resume: some activity pausing.");
-
- // Adding previous activity to the waiting visible list, or it would be stopped
- // before top activity being visible.
- if (prev != null && !next.nowVisible) {
- mStackSupervisor.mActivitiesWaitingForVisibleActivity.add(prev);
- }
return false;
}
@@ -2873,9 +2858,7 @@
// the screen based on the new activity order.
boolean notUpdated = true;
- // Activity should also be visible if set mLaunchTaskBehind to true (see
- // ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
- if (shouldBeVisible(next)) {
+ if (isFocusedStackOnDisplay()) {
// We have special rotation behavior when here is some active activity that
// requests specific orientation or Keyguard is locked. Make sure all activity
// visibilities are set correctly as well as the transition is updated if needed
@@ -4104,12 +4087,6 @@
mStackSupervisor.mFinishingActivities.add(r);
r.resumeKeyDispatchingLocked();
mRootActivityContainer.resumeFocusedStacksTopActivities();
- // If activity was not paused at this point - explicitly pause it to start finishing
- // process. Finishing will be completed once it reports pause back.
- if (r.isState(RESUMED) && mPausingActivity != null) {
- startPausingLocked(false /* userLeaving */, false /* uiSleeping */, next /* resuming */,
- false /* dontWait */);
- }
return r;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index a83ef34..3a288ca 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -327,6 +327,9 @@
*/
PowerManager.WakeLock mGoingToSleep;
+ /** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */
+ boolean inResumeTopActivity;
+
/**
* Temporary rect used during docked stack resize calculation so we don't need to create a new
* object each time.
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 43c1206..2807094 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -179,10 +179,7 @@
.setActivityOptions(options.toBundle())
.execute();
mLastHomeActivityStartRecord = tmpOutRecord[0];
- final ActivityDisplay display =
- mService.mRootActivityContainer.getActivityDisplay(displayId);
- final ActivityStack homeStack = display != null ? display.getHomeStack() : null;
- if (homeStack != null && homeStack.mInResumeTopActivity) {
+ if (mSupervisor.inResumeTopActivity) {
// If we are in resume section already, home activity will be initialized, but not
// resumed (to avoid recursive resume) and will stay that way until something pokes it
// again. We need to schedule another resume.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 937f5d3..865ffbd 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1634,7 +1634,7 @@
// Also, we don't want to resume activities in a task that currently has an overlay
// as the starting activity just needs to be in the visible paused state until the
// over is removed.
- mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS);
+ mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
// Go ahead and tell window manager to execute app transition for this activity
// since the app transition will not be triggered through the resume channel.
mTargetStack.getDisplay().mDisplayContent.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 61c4863..ea6f4cc 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5313,9 +5313,18 @@
}
void updateActivityUsageStats(ActivityRecord activity, int event) {
+ ComponentName taskRoot = null;
+ final TaskRecord task = activity.getTaskRecord();
+ if (task != null) {
+ final ActivityRecord rootActivity = task.getRootActivity();
+ if (rootActivity != null) {
+ taskRoot = rootActivity.mActivityComponent;
+ }
+ }
+
final Message m = PooledLambda.obtainMessage(
ActivityManagerInternal::updateActivityUsageStats, mAmInternal,
- activity.mActivityComponent, activity.mUserId, event, activity.appToken);
+ activity.mActivityComponent, activity.mUserId, event, activity.appToken, taskRoot);
mH.sendMessage(m);
}
@@ -6213,30 +6222,27 @@
}
return;
}
- mH.post(() -> {
- synchronized (mGlobalLock) {
- final ActivityDisplay activityDisplay =
- mRootActivityContainer.getActivityDisplay(displayId);
- if (activityDisplay == null) {
- // Call might come when display is not yet added or has been removed.
- if (DEBUG_CONFIGURATION) {
- Slog.w(TAG, "Trying to update display configuration for non-existing "
- + "displayId=" + displayId);
- }
- return;
+ synchronized (mGlobalLock) {
+ final ActivityDisplay activityDisplay =
+ mRootActivityContainer.getActivityDisplay(displayId);
+ if (activityDisplay == null) {
+ // Call might come when display is not yet added or has been removed.
+ if (DEBUG_CONFIGURATION) {
+ Slog.w(TAG, "Trying to update display configuration for non-existing "
+ + "displayId=" + displayId);
}
- final WindowProcessController process = mPidMap.get(pid);
- if (process == null) {
- if (DEBUG_CONFIGURATION) {
- Slog.w(TAG, "Trying to update display configuration for invalid "
- + "process, pid=" + pid);
- }
- return;
- }
- process.registerDisplayConfigurationListenerLocked(activityDisplay);
+ return;
}
- });
-
+ final WindowProcessController process = mPidMap.get(pid);
+ if (process == null) {
+ if (DEBUG_CONFIGURATION) {
+ Slog.w(TAG, "Trying to update display configuration for invalid "
+ + "process, pid=" + pid);
+ }
+ return;
+ }
+ process.registerDisplayConfigurationListenerLocked(activityDisplay);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 5f393ef..f3a363a 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -174,6 +174,7 @@
private int mLastUsedAppTransition = TRANSIT_UNSET;
private String mLastOpeningApp;
private String mLastClosingApp;
+ private String mLastChangingApp;
private static final int NEXT_TRANSIT_TYPE_NONE = 0;
private static final int NEXT_TRANSIT_TYPE_CUSTOM = 1;
@@ -318,14 +319,16 @@
private void setAppTransition(int transit, int flags) {
mNextAppTransition = transit;
mNextAppTransitionFlags |= flags;
- setLastAppTransition(TRANSIT_UNSET, null, null);
+ setLastAppTransition(TRANSIT_UNSET, null, null, null);
updateBooster();
}
- void setLastAppTransition(int transit, AppWindowToken openingApp, AppWindowToken closingApp) {
+ void setLastAppTransition(int transit, AppWindowToken openingApp, AppWindowToken closingApp,
+ AppWindowToken changingApp) {
mLastUsedAppTransition = transit;
mLastOpeningApp = "" + openingApp;
mLastClosingApp = "" + closingApp;
+ mLastChangingApp = "" + changingApp;
}
boolean isReady() {
@@ -412,9 +415,7 @@
* @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another
* layout pass needs to be done
*/
- int goodToGo(int transit, AppWindowToken topOpeningApp,
- AppWindowToken topClosingApp, ArraySet<AppWindowToken> openingApps,
- ArraySet<AppWindowToken> closingApps) {
+ int goodToGo(int transit, AppWindowToken topOpeningApp, ArraySet<AppWindowToken> openingApps) {
mNextAppTransition = TRANSIT_UNSET;
mNextAppTransitionFlags = 0;
setAppTransitionState(APP_STATE_RUNNING);
@@ -422,8 +423,6 @@
? topOpeningApp.getAnimation()
: null;
int redoLayout = notifyAppTransitionStartingLocked(transit,
- topOpeningApp != null ? topOpeningApp.token : null,
- topClosingApp != null ? topClosingApp.token : null,
topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
topOpeningAnim != null
? topOpeningAnim.getStatusBarTransitionsStartTime()
@@ -500,13 +499,12 @@
}
}
- private int notifyAppTransitionStartingLocked(int transit, IBinder openToken,
- IBinder closeToken, long duration, long statusBarAnimationStartTime,
- long statusBarAnimationDuration) {
+ private int notifyAppTransitionStartingLocked(int transit, long duration,
+ long statusBarAnimationStartTime, long statusBarAnimationDuration) {
int redoLayout = 0;
for (int i = 0; i < mListeners.size(); i++) {
- redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, openToken,
- closeToken, duration, statusBarAnimationStartTime, statusBarAnimationDuration);
+ redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, duration,
+ statusBarAnimationStartTime, statusBarAnimationDuration);
}
return redoLayout;
}
@@ -2128,6 +2126,8 @@
pw.println(mLastOpeningApp);
pw.print(prefix); pw.print("mLastClosingApp=");
pw.println(mLastClosingApp);
+ pw.print(prefix); pw.print("mLastChangingApp=");
+ pw.println(mLastChangingApp);
}
}
@@ -2226,14 +2226,16 @@
if (dc == null) {
return;
}
- if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty()) {
+ if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty()
+ || !dc.mChangingApps.isEmpty()) {
if (DEBUG_APP_TRANSITIONS) {
Slog.v(TAG_WM, "*** APP TRANSITION TIMEOUT."
+ " displayId=" + dc.getDisplayId()
+ " isTransitionSet()="
+ dc.mAppTransition.isTransitionSet()
+ " mOpeningApps.size()=" + dc.mOpeningApps.size()
- + " mClosingApps.size()=" + dc.mClosingApps.size());
+ + " mClosingApps.size()=" + dc.mClosingApps.size()
+ + " mChangingApps.size()=" + dc.mChangingApps.size());
}
setTimeout();
mService.mWindowPlacerLocked.performSurfacePlacement();
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index bf00ffb..49308b8 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -49,8 +49,8 @@
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.NOTIFY_APP_TRANSITION_STARTING;
+import android.os.SystemClock;
import android.os.Trace;
import android.util.ArraySet;
import android.util.Slog;
@@ -89,8 +89,9 @@
* Handle application transition for given display.
*/
void handleAppTransitionReady() {
- final int appsCount = mDisplayContent.mOpeningApps.size();
- if (!transitionGoodToGo(appsCount, mTempTransitionReasons)) {
+ mTempTransitionReasons.clear();
+ if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
+ || !transitionGoodToGo(mDisplayContent.mChangingApps, mTempTransitionReasons)) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
@@ -108,21 +109,25 @@
mDisplayContent.mWallpaperMayChange = false;
- int i;
- for (i = 0; i < appsCount; i++) {
- final AppWindowToken wtoken = mDisplayContent.mOpeningApps.valueAt(i);
+ int appCount = mDisplayContent.mOpeningApps.size();
+ for (int i = 0; i < appCount; ++i) {
// Clearing the mAnimatingExit flag before entering animation. It's set to true if app
// window is removed, or window relayout to invisible. This also affects window
// visibility. We need to clear it *before* maybeUpdateTransitToWallpaper() as the
// transition selection depends on wallpaper target visibility.
- wtoken.clearAnimatingFlags();
+ mDisplayContent.mOpeningApps.valueAtUnchecked(i).clearAnimatingFlags();
+ }
+ appCount = mDisplayContent.mChangingApps.size();
+ for (int i = 0; i < appCount; ++i) {
+ // Clearing for same reason as above.
+ mDisplayContent.mChangingApps.valueAtUnchecked(i).clearAnimatingFlags();
}
// Adjust wallpaper before we pull the lower/upper target, since pending changes
// (like the clearAnimatingFlags() above) might affect wallpaper target result.
// Or, the opening app window should be a wallpaper target.
mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(
- mDisplayContent.mOpeningApps);
+ mDisplayContent.mOpeningApps, mDisplayContent.mChangingApps);
// Determine if closing and opening app token sets are wallpaper targets, in which case
// special animations are needed.
@@ -141,7 +146,7 @@
// no need to do an animation. This is the case, for example, when this transition is being
// done behind a dream window.
final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps,
- mDisplayContent.mClosingApps);
+ mDisplayContent.mClosingApps, mDisplayContent.mChangingApps);
final boolean allowAnimations = mDisplayContent.getDisplayPolicy().allowAppAnimationsLw();
final AppWindowToken animLpToken = allowAnimations
? findAnimLayoutParamsToken(transit, activityTypes)
@@ -152,11 +157,15 @@
final AppWindowToken topClosingApp = allowAnimations
? getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */)
: null;
+ final AppWindowToken topChangingApp = allowAnimations
+ ? getTopApp(mDisplayContent.mChangingApps, false /* ignoreHidden */)
+ : null;
final WindowManager.LayoutParams animLp = getAnimLp(animLpToken);
overrideWithRemoteAnimationIfSet(animLpToken, transit, activityTypes);
final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mOpeningApps)
- || containsVoiceInteraction(mDisplayContent.mOpeningApps);
+ || containsVoiceInteraction(mDisplayContent.mOpeningApps)
+ || containsVoiceInteraction(mDisplayContent.mChangingApps);
final int layoutRedo;
mService.mSurfaceAnimationRunner.deferStartingAnimations();
@@ -165,13 +174,14 @@
handleClosingApps(transit, animLp, voiceInteraction);
handleOpeningApps(transit, animLp, voiceInteraction);
+ handleChangingApps(transit, animLp, voiceInteraction);
appTransition.setLastAppTransition(transit, topOpeningApp,
- topClosingApp);
+ topClosingApp, topChangingApp);
final int flags = appTransition.getTransitFlags();
layoutRedo = appTransition.goodToGo(transit, topOpeningApp,
- topClosingApp, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps);
+ mDisplayContent.mOpeningApps);
handleNonAppWindowsInTransition(transit, flags);
appTransition.postAnimationCallback();
appTransition.clear();
@@ -183,6 +193,7 @@
mDisplayContent.mOpeningApps.clear();
mDisplayContent.mClosingApps.clear();
+ mDisplayContent.mChangingApps.clear();
mDisplayContent.mUnknownAppVisibilityController.clear();
// This has changed the visibility of windows, so perform
@@ -191,8 +202,8 @@
mDisplayContent.computeImeTarget(true /* updateImeTarget */);
- mService.mH.obtainMessage(NOTIFY_APP_TRANSITION_STARTING,
- mTempTransitionReasons.clone()).sendToTarget();
+ mService.mAtmInternal.notifyAppTransitionStarting(mTempTransitionReasons.clone(),
+ SystemClock.uptimeMillis());
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
@@ -237,29 +248,30 @@
AppWindowToken result;
final ArraySet<AppWindowToken> closingApps = mDisplayContent.mClosingApps;
final ArraySet<AppWindowToken> openingApps = mDisplayContent.mOpeningApps;
+ final ArraySet<AppWindowToken> changingApps = mDisplayContent.mChangingApps;
// Remote animations always win, but fullscreen tokens override non-fullscreen tokens.
- result = lookForHighestTokenWithFilter(closingApps, openingApps,
+ result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
w -> w.getRemoteAnimationDefinition() != null
&& w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
if (result != null) {
return result;
}
- result = lookForHighestTokenWithFilter(closingApps, openingApps,
+ result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
w -> w.fillsParent() && w.findMainWindow() != null);
if (result != null) {
return result;
}
- return lookForHighestTokenWithFilter(closingApps, openingApps,
+ return lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
w -> w.findMainWindow() != null);
}
/**
* @return The set of {@link android.app.WindowConfiguration.ActivityType}s contained in the set
- * of apps in {@code array1} and {@code array2}.
+ * of apps in {@code array1}, {@code array2}, and {@code array3}.
*/
private static ArraySet<Integer> collectActivityTypes(ArraySet<AppWindowToken> array1,
- ArraySet<AppWindowToken> array2) {
+ ArraySet<AppWindowToken> array2, ArraySet<AppWindowToken> array3) {
final ArraySet<Integer> result = new ArraySet<>();
for (int i = array1.size() - 1; i >= 0; i--) {
result.add(array1.valueAt(i).getActivityType());
@@ -267,19 +279,26 @@
for (int i = array2.size() - 1; i >= 0; i--) {
result.add(array2.valueAt(i).getActivityType());
}
+ for (int i = array3.size() - 1; i >= 0; i--) {
+ result.add(array3.valueAt(i).getActivityType());
+ }
return result;
}
private static AppWindowToken lookForHighestTokenWithFilter(ArraySet<AppWindowToken> array1,
- ArraySet<AppWindowToken> array2, Predicate<AppWindowToken> filter) {
- final int array1count = array1.size();
- final int count = array1count + array2.size();
+ ArraySet<AppWindowToken> array2, ArraySet<AppWindowToken> array3,
+ Predicate<AppWindowToken> filter) {
+ final int array2base = array1.size();
+ final int array3base = array2.size() + array2base;
+ final int count = array3base + array3.size();
int bestPrefixOrderIndex = Integer.MIN_VALUE;
AppWindowToken bestToken = null;
for (int i = 0; i < count; i++) {
- final AppWindowToken wtoken = i < array1count
+ final AppWindowToken wtoken = i < array2base
? array1.valueAt(i)
- : array2.valueAt(i - array1count);
+ : (i < array3base
+ ? array2.valueAt(i - array2base)
+ : array3.valueAt(i - array3base));
final int prefixOrderIndex = wtoken.getPrefixOrderIndex();
if (filter.test(wtoken) && prefixOrderIndex > bestPrefixOrderIndex) {
bestPrefixOrderIndex = prefixOrderIndex;
@@ -360,6 +379,24 @@
}
}
+ private void handleChangingApps(int transit, LayoutParams animLp, boolean voiceInteraction) {
+ final ArraySet<AppWindowToken> apps = mDisplayContent.mChangingApps;
+ final int appsCount = apps.size();
+ for (int i = 0; i < appsCount; i++) {
+ AppWindowToken wtoken = apps.valueAt(i);
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now changing app" + wtoken);
+ wtoken.cancelAnimationOnly();
+ wtoken.applyAnimationLocked(null, transit, true, false);
+ wtoken.updateReportedVisibilityLocked();
+ mService.openSurfaceTransaction();
+ try {
+ wtoken.showAllWindowsLocked();
+ } finally {
+ mService.closeSurfaceTransaction("handleChangingApps");
+ }
+ }
+ }
+
private void handleNonAppWindowsInTransition(int transit, int flags) {
if (transit == TRANSIT_KEYGUARD_GOING_AWAY) {
if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
@@ -379,16 +416,15 @@
}
}
- private boolean transitionGoodToGo(int appsCount, SparseIntArray outReasons) {
+ private boolean transitionGoodToGo(ArraySet<AppWindowToken> apps, SparseIntArray outReasons) {
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
- "Checking " + appsCount + " opening apps (frozen="
+ "Checking " + apps.size() + " opening apps (frozen="
+ mService.mDisplayFrozen + " timeout="
+ mDisplayContent.mAppTransition.isTimeout() + ")...");
final ScreenRotationAnimation screenRotationAnimation =
mService.mAnimator.getScreenRotationAnimationLocked(
Display.DEFAULT_DISPLAY);
- outReasons.clear();
if (!mDisplayContent.mAppTransition.isTimeout()) {
// Imagine the case where we are changing orientation due to an app transition, but a
// previous orientation change is still in progress. We won't process the orientation
@@ -404,8 +440,8 @@
}
return false;
}
- for (int i = 0; i < appsCount; i++) {
- AppWindowToken wtoken = mDisplayContent.mOpeningApps.valueAt(i);
+ for (int i = 0; i < apps.size(); i++) {
+ AppWindowToken wtoken = apps.valueAt(i);
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"Check opening app=" + wtoken + ": allDrawn="
+ wtoken.allDrawn + " startingDisplayed="
diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
index bb38f30..29645f6 100644
--- a/services/core/java/com/android/server/wm/AppWindowThumbnail.java
+++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
@@ -50,9 +50,23 @@
private final SurfaceAnimator mSurfaceAnimator;
private final int mWidth;
private final int mHeight;
+ private final boolean mRelative;
AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader) {
+ this(t, appToken, thumbnailHeader, false /* relative */);
+ }
+
+ /**
+ * @param t Transaction to create the thumbnail in.
+ * @param appToken {@link AppWindowToken} to associate this thumbnail with.
+ * @param thumbnailHeader A thumbnail or placeholder for thumbnail to initialize with.
+ * @param relative Whether this thumbnail will be a child of appToken (and thus positioned
+ * relative to it) or not.
+ */
+ AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader,
+ boolean relative) {
mAppToken = appToken;
+ mRelative = relative;
mSurfaceAnimator =
new SurfaceAnimator(this, this::onAnimationFinished, appToken.mWmService);
mWidth = thumbnailHeader.getWidth();
@@ -86,6 +100,9 @@
// We parent the thumbnail to the task, and just place it on top of anything else in the
// task.
t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
+ if (relative) {
+ t.reparent(mSurfaceControl, appToken.getSurfaceControl());
+ }
}
void startAnimation(Transaction t, Animation anim) {
@@ -101,6 +118,13 @@
mAppToken.mWmService.mSurfaceAnimationRunner), false /* hidden */);
}
+ /**
+ * Start animation with existing adapter.
+ */
+ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
+ mSurfaceAnimator.startAnimation(t, anim, hidden);
+ }
+
private void onAnimationFinished() {
}
@@ -147,6 +171,9 @@
@Override
public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
t.setLayer(leash, Integer.MAX_VALUE);
+ if (mRelative) {
+ t.reparent(leash, mAppToken.getSurfaceControl());
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 780eda49..65b36a0 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -25,7 +26,6 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -34,6 +34,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS;
+import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
import static android.view.WindowManager.TRANSIT_UNSET;
import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN;
@@ -98,6 +99,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
+import android.util.ArraySet;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
@@ -118,6 +120,7 @@
import com.android.server.display.ColorDisplayService;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.policy.WindowManagerPolicy.StartingSurface;
+import com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord;
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
@@ -262,7 +265,18 @@
/** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */
private boolean mLastSurfaceShowing = true;
+ /**
+ * This gets used during some open/close transitions as well as during a change transition
+ * where it represents the starting-state snapshot.
+ */
private AppWindowThumbnail mThumbnail;
+ private final Rect mTransitStartRect = new Rect();
+
+ /**
+ * This leash is used to "freeze" the app surface in place after the state change, but before
+ * the animation is ready to start.
+ */
+ private SurfaceControl mTransitChangeLeash = null;
/** Have we been asked to have this token keep the screen frozen? */
private boolean mFreezingScreen;
@@ -273,6 +287,7 @@
private final Point mTmpPoint = new Point();
private final Rect mTmpRect = new Rect();
+ private final Rect mTmpPrevBounds = new Rect();
private RemoteAnimationDefinition mRemoteAnimationDefinition;
private AnimatingAppWindowTokenRegistry mAnimatingAppWindowTokenRegistry;
@@ -591,9 +606,7 @@
delayed = runningAppAnimation = true;
}
final WindowState window = findMainWindow();
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (window != null && accessibilityController != null
- && getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) {
+ if (window != null && accessibilityController != null) {
accessibilityController.onAppWindowTransitionLocked(window, transit);
}
changed = true;
@@ -813,6 +826,7 @@
boolean delayed = commitVisibility(null, false, TRANSIT_UNSET, true, mVoiceInteraction);
getDisplayContent().mOpeningApps.remove(this);
+ getDisplayContent().mChangingApps.remove(this);
getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
mWmService.mTaskSnapshotController.onAppRemoved(this);
waitingToShow = false;
@@ -1531,6 +1545,7 @@
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
final int prevWinMode = getWindowingMode();
+ mTmpPrevBounds.set(getBounds());
super.onConfigurationChanged(newParentConfig);
final int winMode = getWindowingMode();
@@ -1562,9 +1577,68 @@
mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(this,
stackBounds);
}
+ } else if (shouldStartChangeTransition(prevWinMode, winMode)) {
+ initializeChangeTransition(mTmpPrevBounds);
}
}
+ private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) {
+ if (!isVisible() || getDisplayContent().mAppTransition.isTransitionSet()) {
+ return false;
+ }
+ // Only do an animation into and out-of freeform mode for now. Other mode
+ // transition animations are currently handled by system-ui.
+ return (prevWinMode == WINDOWING_MODE_FREEFORM) != (newWinMode == WINDOWING_MODE_FREEFORM);
+ }
+
+ /**
+ * Initializes a change transition. Because the app is visible already, there is a small period
+ * of time where the user can see the app content/window update before the transition starts.
+ * To prevent this, we immediately take a snapshot and place the app/snapshot into a leash which
+ * "freezes" the location/crop until the transition starts.
+ * <p>
+ * Here's a walk-through of the process:
+ * 1. Create a temporary leash ("interim-change-leash") and reparent the app to it.
+ * 2. Set the temporary leash's position/crop to the current state.
+ * 3. Create a snapshot and place that at the top of the leash to cover up content changes.
+ * 4. Once the transition is ready, it will reparent the app to the animation leash.
+ * 5. Detach the interim-change-leash.
+ */
+ private void initializeChangeTransition(Rect startBounds) {
+ mDisplayContent.prepareAppTransition(TRANSIT_TASK_CHANGE_WINDOWING_MODE,
+ false /* alwaysKeepCurrent */, 0, false /* forceOverride */);
+ mDisplayContent.mChangingApps.add(this);
+ mTransitStartRect.set(startBounds);
+
+ final SurfaceControl.Builder builder = makeAnimationLeash()
+ .setParent(getAnimationLeashParent())
+ .setName(getSurfaceControl() + " - interim-change-leash");
+ mTransitChangeLeash = builder.build();
+ Transaction t = getPendingTransaction();
+ t.setWindowCrop(mTransitChangeLeash, startBounds.width(), startBounds.height());
+ t.setPosition(mTransitChangeLeash, startBounds.left, startBounds.top);
+ t.show(mTransitChangeLeash);
+ t.reparent(getSurfaceControl(), mTransitChangeLeash);
+ onAnimationLeashCreated(t, mTransitChangeLeash);
+
+ if (mThumbnail == null && getTask() != null) {
+ final TaskSnapshotController snapshotCtrl = mWmService.mTaskSnapshotController;
+ final ArraySet<Task> tasks = new ArraySet<>();
+ tasks.add(getTask());
+ snapshotCtrl.snapshotTasks(tasks);
+ snapshotCtrl.addSkipClosingAppSnapshotTasks(tasks);
+ final ActivityManager.TaskSnapshot snapshot = snapshotCtrl.getSnapshot(
+ getTask().mTaskId, getTask().mUserId, false /* restoreFromDisk */,
+ false /* reducedResolution */);
+ mThumbnail = new AppWindowThumbnail(t, this, snapshot.getSnapshot(),
+ true /* relative */);
+ }
+ }
+
+ boolean isInChangeTransition() {
+ return mTransitChangeLeash != null || isChangeTransition(mTransit);
+ }
+
@Override
void checkAppWindowsReadyToShow() {
if (allDrawn == mLastAllDrawn) {
@@ -1975,7 +2049,7 @@
} else if (newTask || !processRunning || (taskSwitch && !activityCreated)) {
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else if (taskSwitch && allowTaskSnapshot) {
- if (mWmService.mLowRamTaskSnapshots) {
+ if (mWmService.mLowRamTaskSnapshotsAndRecents) {
// For low RAM devices, we use the splash screen starting window instead of the
// task snapshot starting window.
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
@@ -2245,6 +2319,15 @@
return getBounds();
}
+ private static boolean isChangeTransition(int transit) {
+ return transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE;
+ }
+
+ private int getDefaultChangeTransitionDuration() {
+ return (int) (AppTransition.DEFAULT_APP_TRANSITION_DURATION
+ * mWmService.getTransitionAnimationScaleLocked());
+ }
+
boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter,
boolean isVoiceInteraction) {
@@ -2263,13 +2346,35 @@
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#applyAnimationLocked");
if (okToAnimate()) {
final AnimationAdapter adapter;
+ AnimationAdapter thumbnailAdapter = null;
getAnimationBounds(mTmpPoint, mTmpRect);
+ boolean isChanging = isChangeTransition(transit) && mThumbnail != null;
+
// Delaying animation start isn't compatible with remote animations at all.
if (getDisplayContent().mAppTransition.getRemoteAnimationController() != null
&& !mSurfaceAnimator.isAnimationStartDelayed()) {
- adapter = getDisplayContent().mAppTransition.getRemoteAnimationController()
- .createAnimationAdapter(this, mTmpPoint, mTmpRect);
+ RemoteAnimationRecord adapters =
+ getDisplayContent().mAppTransition.getRemoteAnimationController()
+ .createRemoteAnimationRecord(this, mTmpPoint, mTmpRect,
+ (isChanging ? mTransitStartRect : null));
+ adapter = adapters.mAdapter;
+ thumbnailAdapter = adapters.mThumbnailAdapter;
+ } else if (isChanging) {
+ int duration = getDefaultChangeTransitionDuration();
+ mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
+ adapter = new LocalAnimationAdapter(
+ new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
+ getDisplayContent().getDisplayInfo(), duration,
+ true /* isAppAnimation */, false /* isThumbnail */),
+ mWmService.mSurfaceAnimationRunner);
+ thumbnailAdapter = new LocalAnimationAdapter(
+ new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
+ getDisplayContent().getDisplayInfo(), duration,
+ true /* isAppAnimation */, true /* isThumbnail */),
+ mWmService.mSurfaceAnimationRunner);
+ mTransit = transit;
+ mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
} else {
final int appStackClipMode =
getDisplayContent().mAppTransition.getAppStackClipMode();
@@ -2297,6 +2402,10 @@
if (adapter.getShowWallpaper()) {
mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
+ if (thumbnailAdapter != null) {
+ mThumbnail.startAnimation(
+ getPendingTransaction(), thumbnailAdapter, !isVisible());
+ }
}
} else {
cancelAnimation();
@@ -2407,7 +2516,7 @@
@Override
protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
if (!mSurfaceAnimator.hasLeash()) {
- t.reparent(mSurfaceControl, newParent.getHandle());
+ t.reparent(mSurfaceControl, newParent);
}
}
@@ -2432,6 +2541,17 @@
final DisplayContent dc = getDisplayContent();
dc.assignStackOrdering();
+
+ if (leash == mTransitChangeLeash) {
+ // This is a temporary state so skip any animation notifications
+ return;
+ } else if (mTransitChangeLeash != null) {
+ // unparent mTransitChangeLeash for clean-up
+ t.hide(mTransitChangeLeash);
+ t.reparent(mTransitChangeLeash, null);
+ mTransitChangeLeash = null;
+ }
+
if (mAnimatingAppWindowTokenRegistry != null) {
mAnimatingAppWindowTokenRegistry.notifyStarting(this);
}
@@ -2453,7 +2573,7 @@
t.setWindowCrop(mAnimationBoundsLayer, mTmpRect);
// Reparent leash to animation bounds layer.
- t.reparent(leash, mAnimationBoundsLayer.getHandle());
+ t.reparent(leash, mAnimationBoundsLayer);
}
}
@@ -2490,6 +2610,12 @@
+ " okToAnimate=" + okToAnimate()
+ " startingDisplayed=" + startingDisplayed);
+ // clean up thumbnail window
+ if (mThumbnail != null) {
+ mThumbnail.destroy();
+ mThumbnail = null;
+ }
+
// WindowState.onExitAnimationDone might modify the children list, so make a copy and then
// traverse the copy.
final ArrayList<WindowState> children = new ArrayList<>(mChildren);
@@ -2521,14 +2647,30 @@
@Override
void cancelAnimation() {
- super.cancelAnimation();
+ cancelAnimationOnly();
clearThumbnail();
+ if (mTransitChangeLeash != null) {
+ getPendingTransaction().hide(mTransitChangeLeash);
+ getPendingTransaction().reparent(mTransitChangeLeash, null);
+ mTransitChangeLeash = null;
+ }
+ }
+
+ /**
+ * This only cancels the animation. It doesn't do other teardown like cleaning-up thumbnail
+ * or interim leashes.
+ * <p>
+ * Used when canceling in preparation for starting a new animation.
+ */
+ void cancelAnimationOnly() {
+ super.cancelAnimation();
}
boolean isWaitingForTransitionStart() {
return getDisplayContent().mAppTransition.isTransitionSet()
&& (getDisplayContent().mOpeningApps.contains(this)
- || getDisplayContent().mClosingApps.contains(this));
+ || getDisplayContent().mClosingApps.contains(this)
+ || getDisplayContent().mChangingApps.contains(this));
}
public int getTransit() {
@@ -2838,6 +2980,7 @@
void removeFromPendingTransition() {
if (isWaitingForTransitionStart() && mDisplayContent != null) {
mDisplayContent.mOpeningApps.remove(this);
+ mDisplayContent.mChangingApps.remove(this);
mDisplayContent.mClosingApps.remove(this);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6527ca0..8f976e7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -30,11 +30,14 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_PRIVATE;
import static android.view.InsetsState.TYPE_IME;
+import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.TYPE_TOP_BAR;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.View.GONE;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_TOP;
@@ -164,6 +167,7 @@
import android.view.SurfaceControl.Transaction;
import android.view.SurfaceSession;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
@@ -246,6 +250,7 @@
final ArraySet<AppWindowToken> mOpeningApps = new ArraySet<>();
final ArraySet<AppWindowToken> mClosingApps = new ArraySet<>();
+ final ArraySet<AppWindowToken> mChangingApps = new ArraySet<>();
final UnknownAppVisibilityController mUnknownAppVisibilityController;
BoundsAnimationController mBoundsAnimationController;
@@ -1051,6 +1056,11 @@
*/
void setInsetProvider(@InternalInsetType int type, WindowState win,
@Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider) {
+ if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL) {
+ if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) {
+ return;
+ }
+ }
mInsetsStateController.getSourceProvider(type).setWindow(win, frameProvider);
}
@@ -1457,11 +1467,9 @@
}
}
- // TODO (multi-display): Magnification is supported only for the default display.
// Announce rotation only if we will not animate as we already have the
// windows in final state. Otherwise, we make this call at the rotation end.
- if (screenRotationAnimation == null && mWmService.mAccessibilityController != null
- && isDefaultDisplay) {
+ if (screenRotationAnimation == null && mWmService.mAccessibilityController != null) {
mWmService.mAccessibilityController.onRotationChangedLocked(this);
}
}
@@ -2447,6 +2455,7 @@
// Clear all transitions & screen frozen states when removing display.
mOpeningApps.clear();
mClosingApps.clear();
+ mChangingApps.clear();
mUnknownAppVisibilityController.clear();
mAppTransition.removeAppTransitionTimeoutCallbacks();
handleAnimatingStoppedAndTransition();
@@ -3277,7 +3286,7 @@
}
}
- if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty()) {
+ if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty() || !mChangingApps.isEmpty()) {
pw.println();
if (mOpeningApps.size() > 0) {
pw.print(" mOpeningApps="); pw.println(mOpeningApps);
@@ -3285,6 +3294,9 @@
if (mClosingApps.size() > 0) {
pw.print(" mClosingApps="); pw.println(mClosingApps);
}
+ if (mChangingApps.size() > 0) {
+ pw.print(" mChangingApps="); pw.println(mChangingApps);
+ }
}
mUnknownAppVisibilityController.dump(pw, " ");
@@ -4581,7 +4593,7 @@
* Reparents the given surface to mOverlayLayer.
*/
void reparentToOverlay(Transaction transaction, SurfaceControl surface) {
- transaction.reparent(surface, mOverlayLayer.getHandle());
+ transaction.reparent(surface, mOverlayLayer);
}
void applyMagnificationSpec(MagnificationSpec spec) {
@@ -4824,11 +4836,11 @@
* Re-parent the DisplayContent's top surfaces, {@link #mWindowingLayer} and
* {@link #mOverlayLayer} to the specified surfaceControl.
*
- * @param surfaceControlHandle The handle for the new SurfaceControl, where the DisplayContent's
+ * @param surfaceControlHandle The new SurfaceControl, where the DisplayContent's
* surfaces will be re-parented to.
*/
- void reparentDisplayContent(IBinder surfaceControlHandle) {
- mPendingTransaction.reparent(mWindowingLayer, surfaceControlHandle)
- .reparent(mOverlayLayer, surfaceControlHandle);
+ void reparentDisplayContent(SurfaceControl sc) {
+ mPendingTransaction.reparent(mWindowingLayer, sc)
+ .reparent(mOverlayLayer, sc);
}
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 7ea88bb..ea65dd9 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -36,7 +36,6 @@
import static com.android.server.wm.DockedStackDividerControllerProto.MINIMIZED_DOCK;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED;
import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
import android.content.Context;
@@ -566,9 +565,7 @@
mMaximizeMeetFraction = getClipRevealMeetFraction(stack);
animDuration = (long) (mAnimationDuration * mMaximizeMeetFraction);
}
- mService.mH.removeMessages(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED);
- mService.mH.obtainMessage(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED,
- minimizedDock ? 1 : 0, 0).sendToTarget();
+ mService.mAtmInternal.notifyDockedStackMinimizedChanged(minimizedDock);
final int size = mDockedStackListeners.beginBroadcast();
for (int i = 0; i < size; ++i) {
final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 786a306..3f77e1c 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -120,6 +120,8 @@
// A surface used to catch input events for the drag-and-drop operation.
SurfaceControl mInputSurface;
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
private final Rect mTmpClipRect = new Rect();
/**
@@ -240,7 +242,7 @@
// Clear the internal variables.
if (mSurfaceControl != null) {
- mSurfaceControl.destroy();
+ mTransaction.reparent(mSurfaceControl, null).apply();
mSurfaceControl = null;
}
if (mAnimator != null && !mAnimationCompleted) {
@@ -500,18 +502,13 @@
mCurrentY = y;
// Move the surface to the given touch
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
- TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
- mService.openSurfaceTransaction();
- try {
- mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
- if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " DRAG "
- + mSurfaceControl + ": pos=(" +
- (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
- } finally {
- mService.closeSurfaceTransaction("notifyMoveLw");
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
- TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLocked");
+ if (SHOW_LIGHT_TRANSACTIONS) {
+ Slog.i(TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
+ }
+ mTransaction.setPosition(mSurfaceControl, x - mThumbOffsetX, y - mThumbOffsetY).apply();
+ if (SHOW_TRANSACTIONS) {
+ Slog.i(TAG_WM, " DRAG " + mSurfaceControl + ": pos=(" + (int) (x - mThumbOffsetX) + ","
+ + (int) (y - mThumbOffsetY) + ")");
}
notifyLocationLocked(x, y);
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 632db38..3c5d911 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -46,7 +46,6 @@
import android.view.InputEventReceiver;
import android.view.InputWindowHandle;
import android.view.SurfaceControl;
-import android.view.animation.Animation;
import com.android.server.AnimationThread;
import com.android.server.policy.WindowManagerPolicy;
@@ -70,8 +69,7 @@
private boolean mDisableWallpaperTouchEvents;
private final Rect mTmpRect = new Rect();
- private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer =
- new UpdateInputForAllWindowsConsumer();
+ private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer;
private final int mDisplayId;
private final DisplayContent mDisplayContent;
@@ -165,6 +163,7 @@
mDisplayId = displayId;
mInputTransaction = mDisplayContent.getPendingTransaction();
mHandler = AnimationThread.getHandler();
+ mUpdateInputForAllWindowsConsumer = new UpdateInputForAllWindowsConsumer();
}
void onDisplayRemoved() {
@@ -407,6 +406,9 @@
boolean inDrag;
WallpaperController wallpaperController;
+ // An invalid window handle that tells SurfaceFlinger not update the input info.
+ final InputWindowHandle mInvalidInputWindow = new InputWindowHandle(null, null, mDisplayId);
+
private void updateInputWindows(boolean inDrag) {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
@@ -445,6 +447,10 @@
final InputWindowHandle inputWindowHandle = w.mInputWindowHandle;
if (inputChannel == null || inputWindowHandle == null || w.mRemoved
|| w.cantReceiveTouchInput()) {
+ if (w.mWinAnimator.hasSurface()) {
+ mInputTransaction.setInputWindowInfo(
+ w.mWinAnimator.mSurfaceController.mSurfaceControl, mInvalidInputWindow);
+ }
// Skip this window because it cannot possibly receive input.
return;
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index d6f1616..e49e4c0 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
@@ -159,7 +161,7 @@
}
boolean isClientVisible() {
- return !ViewRootImpl.USE_NEW_INSETS || mClientVisible;
+ return ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible;
}
private class ControlAdapter implements AnimationAdapter {
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index bc01f7c..32dbe96 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -19,6 +19,9 @@
import static android.view.InsetsState.TYPE_IME;
import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
import static android.view.InsetsState.TYPE_TOP_BAR;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.ViewRootImpl.sNewInsetsMode;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -160,7 +163,7 @@
}
private void onControlChanged(int type, @Nullable WindowState win) {
- if (!ViewRootImpl.USE_NEW_INSETS) {
+ if (sNewInsetsMode == NEW_INSETS_MODE_NONE) {
return;
}
final WindowState previous = mTypeWinControlMap.get(type);
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 83ba384..105ff06 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -29,7 +29,6 @@
import static com.android.server.wm.AnimationAdapterProto.REMOTE;
import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RECENTS_ANIMATIONS;
-import static com.android.server.wm.WindowManagerService.H.NOTIFY_APP_TRANSITION_STARTING;
import android.annotation.IntDef;
import android.app.ActivityManager.TaskSnapshot;
@@ -413,8 +412,7 @@
}
final SparseIntArray reasons = new SparseIntArray();
reasons.put(WINDOWING_MODE_FULLSCREEN, APP_TRANSITION_RECENTS_ANIM);
- mService.mH.obtainMessage(NOTIFY_APP_TRANSITION_STARTING,
- reasons).sendToTarget();
+ mService.mAtmInternal.notifyAppTransitionStarting(reasons, SystemClock.uptimeMillis());
}
void cancelAnimation(@ReorderMode int reorderMode, String reason) {
@@ -626,7 +624,7 @@
mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash,
!topApp.fillsParent(), mainWindow.mWinAnimator.mLastClipRect,
insets, mTask.getPrefixOrderIndex(), mPosition, mBounds,
- mTask.getWindowConfiguration(), mIsRecentTaskInvisible);
+ mTask.getWindowConfiguration(), mIsRecentTaskInvisible, null, null);
return mTarget;
}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index f5acdcc..f760b39 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -57,7 +57,7 @@
private final WindowManagerService mService;
private final RemoteAnimationAdapter mRemoteAnimationAdapter;
- private final ArrayList<RemoteAnimationAdapterWrapper> mPendingAnimations = new ArrayList<>();
+ private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>();
private final Rect mTmpRect = new Rect();
private final Handler mHandler;
private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable");
@@ -74,21 +74,22 @@
}
/**
- * Creates an animation for each individual {@link AppWindowToken}.
+ * Creates an animation record for each individual {@link AppWindowToken}.
*
* @param appWindowToken The app to animate.
* @param position The position app bounds, in screen coordinates.
- * @param stackBounds The stack bounds of the app.
- * @return The adapter to be run on the app.
+ * @param stackBounds The stack bounds of the app relative to position.
+ * @param startBounds The stack bounds before the transition, in screen coordinates
+ * @return The record representing animation(s) to run on the app.
*/
- AnimationAdapter createAnimationAdapter(AppWindowToken appWindowToken, Point position,
- Rect stackBounds) {
+ RemoteAnimationRecord createRemoteAnimationRecord(AppWindowToken appWindowToken,
+ Point position, Rect stackBounds, Rect startBounds) {
if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimationAdapter(): token="
+ appWindowToken);
- final RemoteAnimationAdapterWrapper adapter = new RemoteAnimationAdapterWrapper(
- appWindowToken, position, stackBounds);
- mPendingAnimations.add(adapter);
- return adapter;
+ final RemoteAnimationRecord adapters =
+ new RemoteAnimationRecord(appWindowToken, position, stackBounds, startBounds);
+ mPendingAnimations.add(adapters);
+ return adapters;
}
/**
@@ -148,7 +149,7 @@
final StringWriter sw = new StringWriter();
final FastPrintWriter pw = new FastPrintWriter(sw);
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- mPendingAnimations.get(i).dump(pw, "");
+ mPendingAnimations.get(i).mAdapter.dump(pw, "");
}
pw.close();
Slog.i(TAG, sw.toString());
@@ -158,19 +159,26 @@
if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimations()");
final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- final RemoteAnimationAdapterWrapper wrapper = mPendingAnimations.get(i);
- final RemoteAnimationTarget target = wrapper.createRemoteAppAnimation();
+ final RemoteAnimationRecord wrappers = mPendingAnimations.get(i);
+ final RemoteAnimationTarget target = wrappers.createRemoteAnimationTarget();
if (target != null) {
- if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tAdd token=" + wrapper.mAppWindowToken);
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tAdd token=" + wrappers.mAppWindowToken);
targets.add(target);
} else {
if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tRemove token="
- + wrapper.mAppWindowToken);
+ + wrappers.mAppWindowToken);
// We can't really start an animation but we still need to make sure to finish the
// pending animation that was started by SurfaceAnimator
- if (wrapper.mCapturedFinishCallback != null) {
- wrapper.mCapturedFinishCallback.onAnimationFinished(wrapper);
+ if (wrappers.mAdapter != null
+ && wrappers.mAdapter.mCapturedFinishCallback != null) {
+ wrappers.mAdapter.mCapturedFinishCallback
+ .onAnimationFinished(wrappers.mAdapter);
+ }
+ if (wrappers.mThumbnailAdapter != null
+ && wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) {
+ wrappers.mThumbnailAdapter.mCapturedFinishCallback
+ .onAnimationFinished(wrappers.mThumbnailAdapter);
}
mPendingAnimations.remove(i);
}
@@ -190,9 +198,16 @@
if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG,
"onAnimationFinished(): Notify animation finished:");
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- final RemoteAnimationAdapterWrapper adapter = mPendingAnimations.get(i);
- adapter.mCapturedFinishCallback.onAnimationFinished(adapter);
- if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapter.mAppWindowToken);
+ final RemoteAnimationRecord adapters = mPendingAnimations.get(i);
+ if (adapters.mAdapter != null) {
+ adapters.mAdapter.mCapturedFinishCallback
+ .onAnimationFinished(adapters.mAdapter);
+ }
+ if (adapters.mThumbnailAdapter != null) {
+ adapters.mThumbnailAdapter.mCapturedFinishCallback
+ .onAnimationFinished(adapters.mThumbnailAdapter);
+ }
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapters.mAppWindowToken);
}
} catch (Exception e) {
Slog.e(TAG, "Failed to finish remote animation", e);
@@ -282,47 +297,84 @@
}
};
- private class RemoteAnimationAdapterWrapper implements AnimationAdapter {
+ /**
+ * Contains information about a remote-animation for one AppWindowToken. This keeps track of,
+ * potentially, multiple animating surfaces (AdapterWrappers) associated with one
+ * Window/Transition. For example, a change transition has an adapter controller for the
+ * main window and an adapter controlling the start-state snapshot.
+ * <p>
+ * This can be thought of as a bridge between the information that the remote animator sees (via
+ * {@link RemoteAnimationTarget}) and what the server sees (the
+ * {@link RemoteAnimationAdapterWrapper}(s) interfacing with the moving surfaces).
+ */
+ public class RemoteAnimationRecord {
+ RemoteAnimationAdapterWrapper mAdapter;
+ RemoteAnimationAdapterWrapper mThumbnailAdapter = null;
+ RemoteAnimationTarget mTarget;
+ final AppWindowToken mAppWindowToken;
+ final Rect mStartBounds;
- private final AppWindowToken mAppWindowToken;
- private SurfaceControl mCapturedLeash;
- private OnAnimationFinishedCallback mCapturedFinishCallback;
- private final Point mPosition = new Point();
- private final Rect mStackBounds = new Rect();
- private RemoteAnimationTarget mTarget;
-
- RemoteAnimationAdapterWrapper(AppWindowToken appWindowToken, Point position,
- Rect stackBounds) {
+ RemoteAnimationRecord(AppWindowToken appWindowToken, Point endPos, Rect endBounds,
+ Rect startBounds) {
mAppWindowToken = appWindowToken;
- mPosition.set(position.x, position.y);
- mStackBounds.set(stackBounds);
+ mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, endBounds);
+ if (startBounds != null) {
+ mStartBounds = new Rect(startBounds);
+ mTmpRect.set(startBounds);
+ mTmpRect.offsetTo(0, 0);
+ mThumbnailAdapter =
+ new RemoteAnimationAdapterWrapper(this, new Point(0, 0), mTmpRect);
+ } else {
+ mStartBounds = null;
+ }
}
- RemoteAnimationTarget createRemoteAppAnimation() {
+ RemoteAnimationTarget createRemoteAnimationTarget() {
final Task task = mAppWindowToken.getTask();
final WindowState mainWindow = mAppWindowToken.findMainWindow();
- if (task == null || mainWindow == null || mCapturedFinishCallback == null
- || mCapturedLeash == null) {
+ if (task == null || mainWindow == null || mAdapter == null
+ || mAdapter.mCapturedFinishCallback == null
+ || mAdapter.mCapturedLeash == null) {
return null;
}
final Rect insets = new Rect();
mainWindow.getContentInsets(insets);
InsetUtils.addInsets(insets, mAppWindowToken.getLetterboxInsets());
mTarget = new RemoteAnimationTarget(task.mTaskId, getMode(),
- mCapturedLeash, !mAppWindowToken.fillsParent(),
+ mAdapter.mCapturedLeash, !mAppWindowToken.fillsParent(),
mainWindow.mWinAnimator.mLastClipRect, insets,
- mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds,
- task.getWindowConfiguration(), false /*isNotInRecents*/);
+ mAppWindowToken.getPrefixOrderIndex(), mAdapter.mPosition,
+ mAdapter.mStackBounds, task.getWindowConfiguration(), false /*isNotInRecents*/,
+ mThumbnailAdapter != null ? mThumbnailAdapter.mCapturedLeash : null,
+ mStartBounds);
return mTarget;
}
private int getMode() {
- if (mAppWindowToken.getDisplayContent().mOpeningApps.contains(mAppWindowToken)) {
+ final DisplayContent dc = mAppWindowToken.getDisplayContent();
+ if (dc.mOpeningApps.contains(mAppWindowToken)) {
return RemoteAnimationTarget.MODE_OPENING;
+ } else if (dc.mChangingApps.contains(mAppWindowToken)) {
+ return RemoteAnimationTarget.MODE_CHANGING;
} else {
return RemoteAnimationTarget.MODE_CLOSING;
}
}
+ }
+
+ private class RemoteAnimationAdapterWrapper implements AnimationAdapter {
+ private final RemoteAnimationRecord mRecord;
+ SurfaceControl mCapturedLeash;
+ private OnAnimationFinishedCallback mCapturedFinishCallback;
+ private final Point mPosition = new Point();
+ private final Rect mStackBounds = new Rect();
+
+ RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position,
+ Rect stackBounds) {
+ mRecord = record;
+ mPosition.set(position.x, position.y);
+ mStackBounds.set(stackBounds);
+ }
@Override
public boolean getShowWallpaper() {
@@ -340,7 +392,7 @@
if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation");
// Restore z-layering, position and stack crop until client has a chance to modify it.
- t.setLayer(animationLeash, mAppWindowToken.getPrefixOrderIndex());
+ t.setLayer(animationLeash, mRecord.mAppWindowToken.getPrefixOrderIndex());
t.setPosition(animationLeash, mPosition.x, mPosition.y);
mTmpRect.set(mStackBounds);
mTmpRect.offsetTo(0, 0);
@@ -351,7 +403,14 @@
@Override
public void onAnimationCancelled(SurfaceControl animationLeash) {
- mPendingAnimations.remove(this);
+ if (mRecord.mAdapter == this) {
+ mRecord.mAdapter = null;
+ } else {
+ mRecord.mThumbnailAdapter = null;
+ }
+ if (mRecord.mAdapter == null && mRecord.mThumbnailAdapter == null) {
+ mPendingAnimations.remove(mRecord);
+ }
if (mPendingAnimations.isEmpty()) {
mHandler.removeCallbacks(mTimeoutRunnable);
releaseFinishedCallback();
@@ -373,10 +432,10 @@
@Override
public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("token="); pw.println(mAppWindowToken);
- if (mTarget != null) {
+ pw.print(prefix); pw.print("token="); pw.println(mRecord.mAppWindowToken);
+ if (mRecord.mTarget != null) {
pw.print(prefix); pw.println("Target:");
- mTarget.dump(pw, prefix + " ");
+ mRecord.mTarget.dump(pw, prefix + " ");
} else {
pw.print(prefix); pw.println("Target: null");
}
@@ -385,8 +444,8 @@
@Override
public void writeToProto(ProtoOutputStream proto) {
final long token = proto.start(REMOTE);
- if (mTarget != null) {
- mTarget.writeToProto(proto, TARGET);
+ if (mRecord.mTarget != null) {
+ mRecord.mTarget.writeToProto(proto, TARGET);
}
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index c4a853d..9b72141 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -1108,41 +1108,28 @@
return false;
}
- boolean result = false;
if (targetStack != null && (targetStack.isTopStackOnDisplay()
|| getTopDisplayFocusedStack() == targetStack)) {
- result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
+ return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
}
+ // Resume all top activities in focused stacks on all displays.
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- boolean resumedOnDisplay = false;
final ActivityDisplay display = mActivityDisplays.get(displayNdx);
- for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = display.getChildAt(stackNdx);
- final ActivityRecord topRunningActivity = stack.topRunningActivityLocked();
- if (!stack.isFocusableAndVisible() || topRunningActivity == null) {
- continue;
- }
- if (topRunningActivity.isState(RESUMED)) {
- // Kick off any lingering app transitions form the MoveTaskToFront operation.
- stack.executeAppTransition(targetOptions);
- } else {
- resumedOnDisplay |= topRunningActivity.makeActiveIfNeeded(target);
- }
+ final ActivityStack focusedStack = display.getFocusedStack();
+ if (focusedStack == null) {
+ continue;
}
- if (!resumedOnDisplay) {
- // In cases when there are no valid activities (e.g. device just booted or launcher
- // crashed) it's possible that nothing was resumed on a display. Requesting resume
- // of top activity in focused stack explicitly will make sure that at least home
- // activity is started and resumed, and no recursion occurs.
- final ActivityStack focusedStack = display.getFocusedStack();
- if (focusedStack != null) {
- focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions);
- }
+ final ActivityRecord r = focusedStack.topRunningActivityLocked();
+ if (r == null || !r.isState(RESUMED)) {
+ focusedStack.resumeTopActivityUncheckedLocked(null, null);
+ } else if (r.isState(RESUMED)) {
+ // Kick off any lingering app transitions form the MoveTaskToFront operation.
+ focusedStack.executeAppTransition(targetOptions);
}
}
- return result;
+ return false;
}
void applySleepTokens(boolean applyToStacks) {
diff --git a/services/core/java/com/android/server/wm/StatusBarController.java b/services/core/java/com/android/server/wm/StatusBarController.java
index b4de75b..6db606d 100644
--- a/services/core/java/com/android/server/wm/StatusBarController.java
+++ b/services/core/java/com/android/server/wm/StatusBarController.java
@@ -61,9 +61,8 @@
}
@Override
- public int onAppTransitionStartingLocked(int transit, IBinder openToken,
- IBinder closeToken, long duration, long statusBarAnimationStartTime,
- long statusBarAnimationDuration) {
+ public int onAppTransitionStartingLocked(int transit, long duration,
+ long statusBarAnimationStartTime, long statusBarAnimationDuration) {
mHandler.post(() -> {
StatusBarManagerInternal statusBar = getStatusBarInternal();
if (statusBar != null && mWin != null) {
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 9d9b48a..1a8a911 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -199,7 +199,7 @@
* @see #setLayer
*/
void reparent(Transaction t, SurfaceControl newParent) {
- t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent.getHandle());
+ t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent);
}
/**
@@ -228,8 +228,8 @@
// Cancel source animation, but don't let animation runner cancel the animation.
from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */);
- t.reparent(surface, mLeash.getHandle());
- t.reparent(mLeash, parent.getHandle());
+ t.reparent(surface, mLeash);
+ t.reparent(mLeash, parent);
mAnimatable.onAnimationLeashCreated(t, mLeash);
mService.mAnimationTransferMap.put(mAnimation, this);
}
@@ -275,7 +275,7 @@
final boolean destroy = mLeash != null && surface != null && parent != null;
if (destroy) {
if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent");
- t.reparent(surface, parent.getHandle());
+ t.reparent(surface, parent);
scheduleAnim = true;
}
mService.mAnimationTransferMap.remove(mAnimation);
@@ -308,7 +308,7 @@
if (!hidden) {
t.show(leash);
}
- t.reparent(surface, leash.getHandle());
+ t.reparent(surface, leash);
return leash;
}
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 0529ed1..69dcaf4 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -698,14 +698,6 @@
return false;
}
- final boolean toTopOfStack = position == MAX_VALUE;
- if (toTopOfStack && toStack.getResumedActivity() != null
- && toStack.topRunningActivityLocked() != null) {
- // Pause the resumed activity on the target stack while re-parenting task on top of it.
- toStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
- null /* resuming */, false /* pauseImmediately */);
- }
-
final int toStackWindowingMode = toStack.getWindowingMode();
final ActivityRecord topActivity = getTopActivity();
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 01a5622..beb3d82 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS;
-import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -90,9 +89,8 @@
private final WindowManagerService mService;
private final TaskSnapshotCache mCache;
- private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
- Environment::getDataSystemCeDirectory);
- private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
+ private final TaskSnapshotPersister mPersister;
+ private final TaskSnapshotLoader mLoader;
private final ArraySet<Task> mSkipClosingAppSnapshotTasks = new ArraySet<>();
private final ArraySet<Task> mTmpTasks = new ArraySet<>();
private final Handler mHandler = new Handler();
@@ -116,6 +114,8 @@
TaskSnapshotController(WindowManagerService service) {
mService = service;
+ mPersister = new TaskSnapshotPersister(mService, Environment::getDataSystemCeDirectory);
+ mLoader = new TaskSnapshotLoader(mPersister);
mCache = new TaskSnapshotCache(mService, mLoader);
mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_LEANBACK);
@@ -270,7 +270,7 @@
}
final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
- final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
+ final float scaleFraction = isLowRamDevice ? mPersister.getReducedScale() : 1f;
task.getBounds(mTmpRect);
mTmpRect.offsetTo(0, 0);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
index 0e1570b..d30843b 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static com.android.server.wm.TaskSnapshotPersister.*;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -92,7 +91,7 @@
proto.topActivityComponent);
return new TaskSnapshot(topActivityComponent, buffer, proto.orientation,
new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom),
- reducedResolution, reducedResolution ? REDUCED_SCALE : 1f,
+ reducedResolution, reducedResolution ? mPersister.getReducedScale() : 1f,
proto.isRealSnapshot, proto.windowingMode, proto.systemUiVisibility,
proto.isTranslucent);
} catch (IOException e) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 24b5b61..e6d646c 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -52,7 +52,9 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
private static final String SNAPSHOTS_DIRNAME = "snapshots";
private static final String REDUCED_POSTFIX = "_reduced";
- static final float REDUCED_SCALE = ActivityManager.isLowRamDeviceStatic() ? 0.6f : 0.5f;
+ private static final float REDUCED_SCALE = .5f;
+ private static final float LOW_RAM_REDUCED_SCALE = .6f;
+ private static final float LOW_RAM_RECENTS_REDUCED_SCALE = .1f;
static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic();
private static final long DELAY_MS = 100;
private static final int QUALITY = 95;
@@ -71,6 +73,7 @@
private boolean mStarted;
private final Object mLock = new Object();
private final DirectoryResolver mDirectoryResolver;
+ private final float mReducedScale;
/**
* The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
@@ -79,8 +82,16 @@
@GuardedBy("mLock")
private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>();
- TaskSnapshotPersister(DirectoryResolver resolver) {
+ TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) {
mDirectoryResolver = resolver;
+ if (service.mLowRamTaskSnapshotsAndRecents) {
+ // Use very low res snapshots if we are using Go version of recents.
+ mReducedScale = LOW_RAM_RECENTS_REDUCED_SCALE;
+ } else {
+ // TODO(122671846) Replace the low RAM value scale with the above when it is fully built
+ mReducedScale = ActivityManager.isLowRamDeviceStatic()
+ ? LOW_RAM_REDUCED_SCALE : REDUCED_SCALE;
+ }
}
/**
@@ -144,6 +155,15 @@
}
}
+ /**
+ * Gets the scaling the persister uses for low resolution task snapshots.
+ *
+ * @return the reduced scale of task snapshots when they are set to be low res
+ */
+ float getReducedScale() {
+ return mReducedScale;
+ }
+
@TestApi
void waitForQueueEmpty() {
while (true) {
@@ -350,8 +370,8 @@
final Bitmap reduced = mSnapshot.isReducedResolution()
? swBitmap
: Bitmap.createScaledBitmap(swBitmap,
- (int) (bitmap.getWidth() * REDUCED_SCALE),
- (int) (bitmap.getHeight() * REDUCED_SCALE), true /* filter */);
+ (int) (bitmap.getWidth() * mReducedScale),
+ (int) (bitmap.getHeight() * mReducedScale), true /* filter */);
try {
FileOutputStream reducedFos = new FileOutputStream(reducedFile);
reduced.compress(JPEG, QUALITY, reducedFos);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 15239c7..c15afc5 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -661,7 +661,8 @@
* Adjusts the wallpaper windows if the input display has a pending wallpaper layout or one of
* the opening apps should be a wallpaper target.
*/
- void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet<AppWindowToken> openingApps) {
+ void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet<AppWindowToken> openingApps,
+ ArraySet<AppWindowToken> changingApps) {
boolean adjust = false;
if ((mDisplayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
adjust = true;
@@ -673,6 +674,15 @@
break;
}
}
+ if (!adjust) {
+ for (int i = changingApps.size() - 1; i >= 0; --i) {
+ final AppWindowToken token = changingApps.valueAt(i);
+ if (token.windowsCanBeWallpaperTarget()) {
+ adjust = true;
+ break;
+ }
+ }
+ }
}
if (adjust) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index b8a0739..b8db98b 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -167,13 +167,11 @@
screenRotationAnimation.kill();
displayAnimator.mScreenRotationAnimation = null;
- //TODO (multidisplay): Accessibility supported only for the default
// display.
- if (accessibilityController != null && dc.isDefaultDisplay) {
+ if (accessibilityController != null) {
// We just finished rotation animation which means we did not
// announce the rotation and waited for it to end, announce now.
- accessibilityController.onRotationChangedLocked(
- mService.getDefaultDisplayContentLocked());
+ accessibilityController.onRotationChangedLocked(dc);
}
}
}
@@ -197,9 +195,8 @@
screenRotationAnimation.updateSurfaces(mTransaction);
}
orAnimating(dc.getDockedDividerController().animate(mCurrentTime));
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (accessibilityController != null && dc.isDefaultDisplay) {
- accessibilityController.drawMagnifiedRegionBorderIfNeededLocked();
+ if (accessibilityController != null) {
+ accessibilityController.drawMagnifiedRegionBorderIfNeededLocked(displayId);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
new file mode 100644
index 0000000..7dd7c4f
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION;
+import static com.android.server.wm.AnimationSpecProto.WINDOW;
+import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
+
+import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
+
+import java.io.PrintWriter;
+
+/**
+ * Animation spec for changing window animations.
+ */
+public class WindowChangeAnimationSpec implements AnimationSpec {
+
+ private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new);
+ private final boolean mIsAppAnimation;
+ private final Rect mStartBounds;
+ private final Rect mEndBounds;
+ private final Rect mTmpRect = new Rect();
+
+ private Animation mAnimation;
+ private final boolean mIsThumbnail;
+
+ public WindowChangeAnimationSpec(Rect startBounds, Rect endBounds, DisplayInfo displayInfo,
+ long duration, boolean isAppAnimation, boolean isThumbnail) {
+ mStartBounds = new Rect(startBounds);
+ mEndBounds = new Rect(endBounds);
+ mIsAppAnimation = isAppAnimation;
+ mIsThumbnail = isThumbnail;
+ createBoundsInterpolator(duration, displayInfo);
+ }
+
+ @Override
+ public boolean getShowWallpaper() {
+ return false;
+ }
+
+ @Override
+ public int getBackgroundColor() {
+ return 0;
+ }
+
+ @Override
+ public long getDuration() {
+ return mAnimation.getDuration();
+ }
+
+ /**
+ * This animator behaves slightly differently depending on whether the window is growing
+ * or shrinking:
+ * If growing, it will do a clip-reveal after quicker fade-out/scale of the smaller (old)
+ * snapshot.
+ * If shrinking, it will do an opposite clip-reveal on the old snapshot followed by a quicker
+ * fade-out of the bigger (old) snapshot while simultaneously shrinking the new window into
+ * place.
+ * @param duration
+ * @param displayInfo
+ */
+ private void createBoundsInterpolator(long duration, DisplayInfo displayInfo) {
+ boolean growing = mEndBounds.width() - mStartBounds.width()
+ + mEndBounds.height() - mStartBounds.height() >= 0;
+ float scalePart = 0.7f;
+ long scalePeriod = (long) (duration * scalePart);
+ float startScaleX = scalePart * ((float) mStartBounds.width()) / mEndBounds.width()
+ + (1.f - scalePart);
+ float startScaleY = scalePart * ((float) mStartBounds.height()) / mEndBounds.height()
+ + (1.f - scalePart);
+ if (mIsThumbnail) {
+ AnimationSet animSet = new AnimationSet(true);
+ Animation anim = new AlphaAnimation(1.f, 0.f);
+ anim.setDuration(scalePeriod);
+ if (!growing) {
+ anim.setStartOffset(duration - scalePeriod);
+ }
+ animSet.addAnimation(anim);
+ float endScaleX = 1.f / startScaleX;
+ float endScaleY = 1.f / startScaleY;
+ anim = new ScaleAnimation(endScaleX, endScaleX, endScaleY, endScaleY);
+ anim.setDuration(duration);
+ animSet.addAnimation(anim);
+ mAnimation = animSet;
+ mAnimation.initialize(mStartBounds.width(), mStartBounds.height(),
+ mEndBounds.width(), mEndBounds.height());
+ } else {
+ AnimationSet animSet = new AnimationSet(true);
+ final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1);
+ scaleAnim.setDuration(scalePeriod);
+ if (!growing) {
+ scaleAnim.setStartOffset(duration - scalePeriod);
+ }
+ animSet.addAnimation(scaleAnim);
+ final Animation translateAnim = new TranslateAnimation(mStartBounds.left,
+ mEndBounds.left, mStartBounds.top, mEndBounds.top);
+ translateAnim.setDuration(duration);
+ animSet.addAnimation(translateAnim);
+ Rect startClip = new Rect(mStartBounds);
+ Rect endClip = new Rect(mEndBounds);
+ startClip.offsetTo(0, 0);
+ endClip.offsetTo(0, 0);
+ final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
+ clipAnim.setDuration(duration);
+ animSet.addAnimation(clipAnim);
+ mAnimation = animSet;
+ mAnimation.initialize(mStartBounds.width(), mStartBounds.height(),
+ displayInfo.appWidth, displayInfo.appHeight);
+ }
+ }
+
+ @Override
+ public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
+ final TmpValues tmp = mThreadLocalTmps.get();
+ if (mIsThumbnail) {
+ mAnimation.getTransformation(currentPlayTime, tmp.mTransformation);
+ t.setMatrix(leash, tmp.mTransformation.getMatrix(), tmp.mFloats);
+ t.setAlpha(leash, tmp.mTransformation.getAlpha());
+ } else {
+ mAnimation.getTransformation(currentPlayTime, tmp.mTransformation);
+ final Matrix matrix = tmp.mTransformation.getMatrix();
+ t.setMatrix(leash, matrix, tmp.mFloats);
+
+ // The following applies an inverse scale to the clip-rect so that it crops "after" the
+ // scale instead of before.
+ tmp.mVecs[1] = tmp.mVecs[2] = 0;
+ tmp.mVecs[0] = tmp.mVecs[3] = 1;
+ matrix.mapVectors(tmp.mVecs);
+ tmp.mVecs[0] = 1.f / tmp.mVecs[0];
+ tmp.mVecs[3] = 1.f / tmp.mVecs[3];
+ final Rect clipRect = tmp.mTransformation.getClipRect();
+ mTmpRect.left = (int) (clipRect.left * tmp.mVecs[0] + 0.5f);
+ mTmpRect.right = (int) (clipRect.right * tmp.mVecs[0] + 0.5f);
+ mTmpRect.top = (int) (clipRect.top * tmp.mVecs[3] + 0.5f);
+ mTmpRect.bottom = (int) (clipRect.bottom * tmp.mVecs[3] + 0.5f);
+ t.setWindowCrop(leash, mTmpRect);
+ }
+ }
+
+ @Override
+ public long calculateStatusBarTransitionStartTime() {
+ long uptime = SystemClock.uptimeMillis();
+ return Math.max(uptime, uptime + ((long) (((float) mAnimation.getDuration()) * 0.99f))
+ - STATUS_BAR_TRANSITION_DURATION);
+ }
+
+ @Override
+ public boolean canSkipFirstFrame() {
+ return false;
+ }
+
+ @Override
+ public boolean needsEarlyWakeup() {
+ return mIsAppAnimation;
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.println(mAnimation.getDuration());
+ }
+
+ @Override
+ public void writeToProtoInner(ProtoOutputStream proto) {
+ final long token = proto.start(WINDOW);
+ proto.write(ANIMATION, mAnimation.toString());
+ proto.end(token);
+ }
+
+ private static class TmpValues {
+ final Transformation mTransformation = new Transformation();
+ final float[] mFloats = new float[9];
+ final float[] mVecs = new float[4];
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 1691dc0..e204697 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -118,8 +118,6 @@
*
* @param transit transition type indicating what kind of transition gets run, must be one
* of AppTransition.TRANSIT_* values
- * @param openToken the token for the opening app
- * @param closeToken the token for the closing app
* @param duration the total duration of the transition
* @param statusBarAnimationStartTime the desired start time for all visual animations in
* the status bar caused by this app transition in uptime millis
@@ -131,8 +129,8 @@
* {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER},
* or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
*/
- public int onAppTransitionStartingLocked(int transit, IBinder openToken, IBinder closeToken,
- long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) {
+ public int onAppTransitionStartingLocked(int transit, long duration,
+ long statusBarAnimationStartTime, long statusBarAnimationDuration) {
return 0;
}
@@ -212,34 +210,40 @@
* and has access to the raw window data while the accessibility layer serves
* as a controller.
*
+ * @param displayId The logical display id.
* @param callbacks The callbacks to invoke.
+ * @return {@code false} if display id is not valid.
*/
- public abstract void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks);
+ public abstract boolean setMagnificationCallbacks(int displayId,
+ @Nullable MagnificationCallbacks callbacks);
/**
* Set by the accessibility layer to specify the magnification and panning to
* be applied to all windows that should be magnified.
*
+ * @param displayId The logical display id.
* @param spec The MagnficationSpec to set.
*
- * @see #setMagnificationCallbacks(MagnificationCallbacks)
+ * @see #setMagnificationCallbacks(int, MagnificationCallbacks)
*/
- public abstract void setMagnificationSpec(MagnificationSpec spec);
+ public abstract void setMagnificationSpec(int displayId, MagnificationSpec spec);
/**
* Set by the accessibility framework to indicate whether the magnifiable regions of the display
* should be shown.
*
+ * @param displayId The logical display id.
* @param show {@code true} to show magnifiable region bounds, {@code false} to hide
*/
- public abstract void setForceShowMagnifiableBounds(boolean show);
+ public abstract void setForceShowMagnifiableBounds(int displayId, boolean show);
/**
* Obtains the magnification regions.
*
+ * @param displayId The logical display id.
* @param magnificationRegion the current magnification region
*/
- public abstract void getMagnificationRegion(@NonNull Region magnificationRegion);
+ public abstract void getMagnificationRegion(int displayId, @NonNull Region magnificationRegion);
/**
* Gets the magnification and translation applied to a window given its token.
@@ -251,7 +255,7 @@
*
* @return The magnification spec for the window.
*
- * @see #setMagnificationCallbacks(MagnificationCallbacks)
+ * @see #setMagnificationCallbacks(int, MagnificationCallbacks)
*/
public abstract MagnificationSpec getCompatibleMagnificationSpecForWindow(
IBinder windowToken);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fda7a85..5eff7d8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -184,7 +184,6 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
@@ -433,11 +432,12 @@
final long mDrawLockTimeoutMillis;
final boolean mAllowAnimationsInLowPowerMode;
+ // TODO(b/122671846) Remove the flag below in favor of isLowRam once feature is stable
/**
* Use very low resolution task snapshots. Replaces task snapshot starting windows with
* splashscreen starting windows. Used on low RAM devices to save memory.
*/
- final boolean mLowRamTaskSnapshots;
+ final boolean mLowRamTaskSnapshotsAndRecents;
final boolean mAllowBootMessages;
@@ -856,12 +856,12 @@
@Override
public void onAppTransitionCancelledLocked(int transit) {
- mH.sendEmptyMessage(H.NOTIFY_APP_TRANSITION_CANCELLED);
+ mAtmInternal.notifyAppTransitionCancelled();
}
@Override
public void onAppTransitionFinishedLocked(IBinder token) {
- mH.sendEmptyMessage(H.NOTIFY_APP_TRANSITION_FINISHED);
+ mAtmInternal.notifyAppTransitionFinished();
final AppWindowToken atoken = mRoot.getAppWindowToken(token);
if (atoken == null) {
return;
@@ -955,7 +955,7 @@
com.android.internal.R.bool.config_disableTransitionAnimation);
mPerDisplayFocusEnabled = context.getResources().getBoolean(
com.android.internal.R.bool.config_perDisplayFocusEnabled);
- mLowRamTaskSnapshots = context.getResources().getBoolean(
+ mLowRamTaskSnapshotsAndRecents = context.getResources().getBoolean(
com.android.internal.R.bool.config_lowRamTaskSnapshotsAndRecents);
mInputManager = inputManager; // Must be before createDisplayContentLocked.
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
@@ -1845,9 +1845,9 @@
synchronized (mGlobalLock) {
if (mAccessibilityController != null) {
WindowState window = mWindowMap.get(token);
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (window != null && window.getDisplayId() == DEFAULT_DISPLAY) {
- mAccessibilityController.onRectangleOnScreenRequestedLocked(rectangle);
+ if (window != null) {
+ mAccessibilityController.onRectangleOnScreenRequestedLocked(
+ window.getDisplayId(), rectangle);
}
}
}
@@ -2236,8 +2236,7 @@
win.mDestroying = true;
win.destroySurface(false, stopped);
}
- // TODO(multidisplay): Magnification is supported only for the default display.
- if (mAccessibilityController != null && win.getDisplayId() == DEFAULT_DISPLAY) {
+ if (mAccessibilityController != null) {
mAccessibilityController.onWindowTransitionLocked(win, transit);
}
@@ -2602,7 +2601,7 @@
@Override
public void notifyKeyguardTrustedChanged() {
- mH.sendEmptyMessage(H.NOTIFY_KEYGUARD_TRUSTED_CHANGED);
+ mAtmInternal.notifyKeyguardTrustedChanged();
}
@Override
@@ -2667,11 +2666,7 @@
* @param callback Runnable to be called when activity manager is done reevaluating visibilities
*/
void notifyKeyguardFlagsChanged(@Nullable Runnable callback, int displayId) {
- final Runnable wrappedCallback = callback != null
- ? () -> { synchronized (mGlobalLock) { callback.run(); } }
- : null;
- mH.obtainMessage(H.NOTIFY_KEYGUARD_FLAGS_CHANGED, displayId, 0,
- wrappedCallback).sendToTarget();
+ mAtmInternal.notifyKeyguardFlagsChanged(callback, displayId);
}
public boolean isKeyguardTrusted() {
@@ -4363,16 +4358,10 @@
public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
- public static final int NOTIFY_APP_TRANSITION_STARTING = 47;
- public static final int NOTIFY_APP_TRANSITION_CANCELLED = 48;
- public static final int NOTIFY_APP_TRANSITION_FINISHED = 49;
public static final int UPDATE_ANIMATION_SCALE = 51;
public static final int WINDOW_HIDE_TIMEOUT = 52;
- public static final int NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED = 53;
public static final int SEAMLESS_ROTATION_TIMEOUT = 54;
public static final int RESTORE_POINTER_ICON = 55;
- public static final int NOTIFY_KEYGUARD_FLAGS_CHANGED = 56;
- public static final int NOTIFY_KEYGUARD_TRUSTED_CHANGED = 57;
public static final int SET_HAS_OVERLAY_UI = 58;
public static final int SET_RUNNING_REMOTE_ANIMATION = 59;
public static final int ANIMATION_FAILSAFE = 60;
@@ -4708,19 +4697,6 @@
}
}
break;
- case NOTIFY_APP_TRANSITION_STARTING: {
- mAtmInternal.notifyAppTransitionStarting((SparseIntArray) msg.obj,
- msg.getWhen());
- }
- break;
- case NOTIFY_APP_TRANSITION_CANCELLED: {
- mAtmInternal.notifyAppTransitionCancelled();
- }
- break;
- case NOTIFY_APP_TRANSITION_FINISHED: {
- mAtmInternal.notifyAppTransitionFinished();
- }
- break;
case WINDOW_HIDE_TIMEOUT: {
final WindowState window = (WindowState) msg.obj;
synchronized (mGlobalLock) {
@@ -4742,10 +4718,6 @@
}
}
break;
- case NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED: {
- mAtmInternal.notifyDockedStackMinimizedChanged(msg.arg1 == 1);
- }
- break;
case RESTORE_POINTER_ICON: {
synchronized (mGlobalLock) {
restorePointerIconLocked((DisplayContent)msg.obj, msg.arg1, msg.arg2);
@@ -4759,14 +4731,6 @@
}
}
break;
- case NOTIFY_KEYGUARD_FLAGS_CHANGED: {
- mAtmInternal.notifyKeyguardFlagsChanged((Runnable) msg.obj, msg.arg1);
- }
- break;
- case NOTIFY_KEYGUARD_TRUSTED_CHANGED: {
- mAtmInternal.notifyKeyguardTrustedChanged();
- }
- break;
case SET_HAS_OVERLAY_UI: {
mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1);
}
@@ -6822,10 +6786,10 @@
}
@Override
- public void setMagnificationSpec(MagnificationSpec spec) {
+ public void setMagnificationSpec(int displayId, MagnificationSpec spec) {
synchronized (mGlobalLock) {
if (mAccessibilityController != null) {
- mAccessibilityController.setMagnificationSpecLocked(spec);
+ mAccessibilityController.setMagnificationSpecLocked(displayId, spec);
} else {
throw new IllegalStateException("Magnification callbacks not set!");
}
@@ -6836,10 +6800,10 @@
}
@Override
- public void setForceShowMagnifiableBounds(boolean show) {
+ public void setForceShowMagnifiableBounds(int displayId, boolean show) {
synchronized (mGlobalLock) {
if (mAccessibilityController != null) {
- mAccessibilityController.setForceShowMagnifiableBoundsLocked(show);
+ mAccessibilityController.setForceShowMagnifiableBoundsLocked(displayId, show);
} else {
throw new IllegalStateException("Magnification callbacks not set!");
}
@@ -6847,10 +6811,11 @@
}
@Override
- public void getMagnificationRegion(@NonNull Region magnificationRegion) {
+ public void getMagnificationRegion(int displayId, @NonNull Region magnificationRegion) {
synchronized (mGlobalLock) {
if (mAccessibilityController != null) {
- mAccessibilityController.getMagnificationRegionLocked(magnificationRegion);
+ mAccessibilityController.getMagnificationRegionLocked(displayId,
+ magnificationRegion);
} else {
throw new IllegalStateException("Magnification callbacks not set!");
}
@@ -6878,16 +6843,19 @@
}
@Override
- public void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks) {
+ public boolean setMagnificationCallbacks(int displayId,
+ @Nullable MagnificationCallbacks callbacks) {
synchronized (mGlobalLock) {
if (mAccessibilityController == null) {
mAccessibilityController = new AccessibilityController(
WindowManagerService.this);
}
- mAccessibilityController.setMagnificationCallbacksLocked(callbacks);
+ boolean result = mAccessibilityController.setMagnificationCallbacksLocked(
+ displayId, callbacks);
if (!mAccessibilityController.hasCallbacksLocked()) {
mAccessibilityController = null;
}
+ return result;
}
}
@@ -7266,8 +7234,12 @@
}, false /* traverseTopToBottom */);
}
- public void applyMagnificationSpec(MagnificationSpec spec) {
- getDefaultDisplayContentLocked().applyMagnificationSpec(spec);
+ /** Called from Accessibility Controller to apply magnification spec */
+ public void applyMagnificationSpecLocked(int displayId, MagnificationSpec spec) {
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ if (displayContent != null) {
+ displayContent.applyMagnificationSpec(spec);
+ }
}
SurfaceControl.Builder makeSurfaceBuilder(SurfaceSession s) {
@@ -7326,7 +7298,7 @@
}
@Override
- public void reparentDisplayContent(int displayId, IBinder surfaceControlHandle) {
+ public void reparentDisplayContent(int displayId, SurfaceControl sc) {
final Display display = mDisplayManager.getDisplay(displayId);
if (display == null) {
throw new IllegalArgumentException(
@@ -7343,7 +7315,7 @@
long token = Binder.clearCallingIdentity();
try {
DisplayContent displayContent = getDisplayContentOrCreate(displayId, null);
- displayContent.reparentDisplayContent(surfaceControlHandle);
+ displayContent.reparentDisplayContent(sc);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 8f86c00..ce5eb84 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1607,8 +1607,7 @@
mWmService.mAccessibilityController;
final int winTransit = TRANSIT_EXIT;
mWinAnimator.applyAnimationLocked(winTransit, false /* isEntrance */);
- //TODO (multidisplay): Magnification is supported only for the default
- if (accessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) {
+ if (accessibilityController != null) {
accessibilityController.onWindowTransitionLocked(this, winTransit);
}
}
@@ -1625,8 +1624,7 @@
if (isVisibleNow()) {
mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
- //TODO (multidisplay): Magnification is supported only for the default
- if (mWmService.mAccessibilityController != null && isDefaultDisplay()) {
+ if (mWmService.mAccessibilityController != null) {
mWmService.mAccessibilityController.onWindowTransitionLocked(this, TRANSIT_EXIT);
}
changed = true;
@@ -1915,9 +1913,7 @@
setDisplayLayoutNeeded();
mWmService.requestTraversal();
}
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (mWmService.mAccessibilityController != null
- && displayId == DEFAULT_DISPLAY) {
+ if (mWmService.mAccessibilityController != null) {
mWmService.mAccessibilityController.onWindowTransitionLocked(this, transit);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index fb5c556..6b4d6d2 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
@@ -1308,9 +1307,7 @@
transit = WindowManagerPolicy.TRANSIT_SHOW;
}
applyAnimationLocked(transit, true);
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null
- && mWin.getDisplayId() == DEFAULT_DISPLAY) {
+ if (mService.mAccessibilityController != null) {
mService.mAccessibilityController.onWindowTransitionLocked(mWin, transit);
}
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index fb00aeb..3729eaf 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -53,6 +53,7 @@
],
include_dirs: [
+ "bionic/libc/private",
"frameworks/base/libs",
"frameworks/native/services",
"system/gatekeeper/include",
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index dc0d53b..159a496 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -24,6 +24,8 @@
#include <sensorservice/SensorService.h>
#include <sensorservicehidl/SensorManager.h>
+#include <bionic_malloc.h>
+
#include <cutils/properties.h>
#include <utils/Log.h>
#include <utils/misc.h>
@@ -64,6 +66,11 @@
ALOGE_IF(err != OK, "Cannot register %s: %d", ISchedulingPolicyService::descriptor, err);
}
+static void android_server_SystemServer_initZygoteChildHeapProfiling(JNIEnv* /* env */,
+ jobject /* clazz */) {
+ android_mallopt(M_INIT_ZYGOTE_CHILD_PROFILING, nullptr, 0);
+}
+
/*
* JNI registration.
*/
@@ -71,6 +78,8 @@
/* name, signature, funcPtr */
{ "startSensorService", "()V", (void*) android_server_SystemServer_startSensorService },
{ "startHidlServices", "()V", (void*) android_server_SystemServer_startHidlServices },
+ { "initZygoteChildHeapProfiling", "()V",
+ (void*) android_server_SystemServer_initZygoteChildHeapProfiling },
};
int register_android_server_SystemServer(JNIEnv* env)
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 90c9cc2..ff0b0d6 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -782,7 +782,7 @@
}
sp<InputWindowHandle> windowHandle =
- android_server_InputWindowHandle_getHandle(env, windowHandleObj);
+ android_view_InputWindowHandle_getHandle(env, windowHandleObj);
if (windowHandle != nullptr) {
windowHandles.push(windowHandle);
}
@@ -822,7 +822,7 @@
void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId,
jobject applicationHandleObj) {
sp<InputApplicationHandle> applicationHandle =
- android_server_InputApplicationHandle_getHandle(env, applicationHandleObj);
+ android_view_InputApplicationHandle_getHandle(env, applicationHandleObj);
mInputManager->getDispatcher()->setFocusedApplication(displayId, applicationHandle);
}
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 918f57e..6e31aed 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -26,8 +26,6 @@
int register_android_server_AlarmManagerService(JNIEnv* env);
int register_android_server_BatteryStatsService(JNIEnv* env);
int register_android_server_ConsumerIrService(JNIEnv *env);
-int register_android_server_InputApplicationHandle(JNIEnv* env);
-int register_android_server_InputWindowHandle(JNIEnv* env);
int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
@@ -74,8 +72,6 @@
register_android_server_broadcastradio_Tuner(vm, env);
register_android_server_PowerManagerService(env);
register_android_server_SerialService(env);
- register_android_server_InputApplicationHandle(env);
- register_android_server_InputWindowHandle(env);
register_android_server_InputManager(env);
register_android_server_LightsService(env);
register_android_server_AlarmManagerService(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index d8225b3..2bf6f35 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -108,12 +108,7 @@
ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback listener) {}
@Override
- public void addCrossProfileCalendarPackage(ComponentName admin, String packageName) {
- }
-
- @Override
- public boolean removeCrossProfileCalendarPackage(ComponentName admin, String packageName) {
- return false;
+ public void setCrossProfileCalendarPackages(ComponentName admin, List<String> packageNames) {
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f8e24ff..23c68b2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -949,8 +949,8 @@
"metered_data_disabled_packages";
private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES =
"cross-profile-calendar-packages";
- private static final String TAG_PACKAGE = "package";
-
+ private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL =
+ "cross-profile-calendar-packages-null";
DeviceAdminInfo info;
@@ -1072,7 +1072,9 @@
String endUserSessionMessage = null;
// The whitelist of packages that can access cross profile calendar APIs.
- final Set<String> mCrossProfileCalendarPackages = new ArraySet<>();
+ // This whitelist should be in default an empty list, which indicates that no package
+ // is whitelisted.
+ List<String> mCrossProfileCalendarPackages = Collections.emptyList();
ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
info = _info;
@@ -1343,11 +1345,12 @@
out.text(endUserSessionMessage);
out.endTag(null, TAG_END_USER_SESSION_MESSAGE);
}
- if (!mCrossProfileCalendarPackages.isEmpty()) {
- out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES);
- writeAttributeValuesToXml(
- out, TAG_PACKAGE, mCrossProfileCalendarPackages);
- out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES);
+ if (mCrossProfileCalendarPackages == null) {
+ out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
+ out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
+ } else {
+ writePackageListToXml(out, TAG_CROSS_PROFILE_CALENDAR_PACKAGES,
+ mCrossProfileCalendarPackages);
}
}
@@ -1542,8 +1545,9 @@
Log.w(LOG_TAG, "Missing text when loading end session message");
}
} else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES.equals(tag)) {
- readAttributeValues(
- parser, TAG_PACKAGE, mCrossProfileCalendarPackages);
+ mCrossProfileCalendarPackages = readPackageList(parser, tag);
+ } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL.equals(tag)) {
+ mCrossProfileCalendarPackages = null;
} else {
Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -1759,8 +1763,10 @@
pw.print(prefix); pw.println("parentAdmin:");
parentAdmin.dump(prefix + " ", pw);
}
- pw.print(prefix); pw.print("mCrossProfileCalendarPackages=");
- pw.println(mCrossProfileCalendarPackages);
+ if (mCrossProfileCalendarPackages != null) {
+ pw.print(prefix); pw.print("mCrossProfileCalendarPackages=");
+ pw.println(mCrossProfileCalendarPackages);
+ }
}
}
@@ -4223,7 +4229,7 @@
@Override
public void setPasswordHistoryLength(ComponentName who, int length, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
Preconditions.checkNotNull(who, "ComponentName is null");
@@ -4246,13 +4252,16 @@
@Override
public int getPasswordHistoryLength(ComponentName who, int userHandle, boolean parent) {
+ if (!mLockPatternUtils.hasSecureLockScreen()) {
+ return 0;
+ }
return getStrictestPasswordRequirement(who, userHandle, parent,
admin -> admin.passwordHistoryLength, PASSWORD_QUALITY_UNSPECIFIED);
}
@Override
public void setPasswordExpirationTimeout(ComponentName who, long timeout, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
Preconditions.checkNotNull(who, "ComponentName is null");
@@ -4288,7 +4297,7 @@
*/
@Override
public long getPasswordExpirationTimeout(ComponentName who, int userHandle, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return 0L;
}
enforceFullCrossUsersPermission(userHandle);
@@ -4423,7 +4432,7 @@
@Override
public long getPasswordExpiration(ComponentName who, int userHandle, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return 0L;
}
enforceFullCrossUsersPermission(userHandle);
@@ -4770,6 +4779,9 @@
@Override
public int getCurrentFailedPasswordAttempts(int userHandle, boolean parent) {
+ if (!mLockPatternUtils.hasSecureLockScreen()) {
+ return 0;
+ }
enforceFullCrossUsersPermission(userHandle);
synchronized (getLockObject()) {
if (!isCallerWithSystemUid()) {
@@ -4789,7 +4801,7 @@
@Override
public void setMaximumFailedPasswordsForWipe(ComponentName who, int num, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
Preconditions.checkNotNull(who, "ComponentName is null");
@@ -4815,7 +4827,7 @@
@Override
public int getMaximumFailedPasswordsForWipe(ComponentName who, int userHandle, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return 0;
}
enforceFullCrossUsersPermission(userHandle);
@@ -4829,7 +4841,7 @@
@Override
public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return UserHandle.USER_NULL;
}
enforceFullCrossUsersPermission(userHandle);
@@ -4910,6 +4922,11 @@
}
@Override
public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException {
+ if (!mLockPatternUtils.hasSecureLockScreen()) {
+ Slog.w(LOG_TAG, "Cannot reset password when the device has no lock screen");
+ return false;
+ }
+
final int callingUid = mInjector.binderGetCallingUid();
final int userHandle = mInjector.userHandleGetCallingUserId();
@@ -5252,7 +5269,7 @@
@Override
public void setRequiredStrongAuthTimeout(ComponentName who, long timeoutMs,
boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
Preconditions.checkNotNull(who, "ComponentName is null");
@@ -5285,7 +5302,7 @@
*/
@Override
public long getRequiredStrongAuthTimeout(ComponentName who, int userId, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS;
}
enforceFullCrossUsersPermission(userId);
@@ -6494,7 +6511,7 @@
*/
@Override
public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
enforceFullCrossUsersPermission(userHandle);
@@ -6514,7 +6531,7 @@
@Override
public void reportPasswordChanged(@UserIdInt int userId) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
enforceFullCrossUsersPermission(userId);
@@ -8790,7 +8807,7 @@
@Override
public void setTrustAgentConfiguration(ComponentName admin, ComponentName agent,
PersistableBundle args, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
Preconditions.checkNotNull(admin, "admin is null");
@@ -8807,7 +8824,7 @@
@Override
public List<PersistableBundle> getTrustAgentConfiguration(ComponentName admin,
ComponentName agent, int userHandle, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return null;
}
Preconditions.checkNotNull(agent, "agent null");
@@ -13009,7 +13026,12 @@
throw new IllegalStateException("logging is not available");
}
if (mNetworkLogger != null) {
- return mNetworkLogger.forceBatchFinalization();
+ final long ident = mInjector.binderClearCallingIdentity();
+ try {
+ return mNetworkLogger.forceBatchFinalization();
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
}
return 0;
}
@@ -13211,7 +13233,7 @@
@Override
public boolean setResetPasswordToken(ComponentName admin, byte[] token) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return false;
}
if (token == null || token.length < 32) {
@@ -13239,7 +13261,7 @@
@Override
public boolean clearResetPasswordToken(ComponentName admin) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return false;
}
synchronized (getLockObject()) {
@@ -13265,6 +13287,9 @@
@Override
public boolean isResetPasswordTokenActive(ComponentName admin) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
+ return false;
+ }
synchronized (getLockObject()) {
final int userHandle = mInjector.userHandleGetCallingUserId();
getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
@@ -13286,6 +13311,9 @@
@Override
public boolean resetPasswordWithToken(ComponentName admin, String passwordOrNull, byte[] token,
int flags) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
+ return false;
+ }
Preconditions.checkNotNull(token);
synchronized (getLockObject()) {
final int userHandle = mInjector.userHandleGetCallingUserId();
@@ -13966,55 +13994,27 @@
}
@Override
- public void addCrossProfileCalendarPackage(ComponentName who, String packageName) {
+ public void setCrossProfileCalendarPackages(ComponentName who, List<String> packageNames) {
if (!mHasFeature) {
return;
}
Preconditions.checkNotNull(who, "ComponentName is null");
- Preconditions.checkStringNotEmpty(packageName, "Package name is null or empty");
synchronized (getLockObject()) {
final ActiveAdmin admin = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (admin.mCrossProfileCalendarPackages.add(packageName)) {
- saveSettingsLocked(mInjector.userHandleGetCallingUserId());
- }
+ admin.mCrossProfileCalendarPackages = packageNames;
+ saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_CALENDAR_PACKAGE)
+ .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CALENDAR_PACKAGES)
.setAdmin(who)
- .setStrings(packageName)
+ .setStrings(packageNames == null ? null
+ : packageNames.toArray(new String[packageNames.size()]))
.write();
}
@Override
- public boolean removeCrossProfileCalendarPackage(ComponentName who, String packageName) {
- if (!mHasFeature) {
- return false;
- }
- Preconditions.checkNotNull(who, "ComponentName is null");
- Preconditions.checkStringNotEmpty(packageName, "Package name is null or empty");
-
- boolean isRemoved = false;
- synchronized (getLockObject()) {
- final ActiveAdmin admin = getActiveAdminForCallerLocked(
- who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- isRemoved = admin.mCrossProfileCalendarPackages.remove(packageName);
- if (isRemoved) {
- saveSettingsLocked(mInjector.userHandleGetCallingUserId());
- }
- }
- if (isRemoved) {
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE)
- .setAdmin(who)
- .setStrings(packageName)
- .write();
- }
- return isRemoved;
- }
-
- @Override
public List<String> getCrossProfileCalendarPackages(ComponentName who) {
if (!mHasFeature) {
return Collections.emptyList();
@@ -14024,7 +14024,7 @@
synchronized (getLockObject()) {
final ActiveAdmin admin = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- return new ArrayList<String>(admin.mCrossProfileCalendarPackages);
+ return admin.mCrossProfileCalendarPackages;
}
}
@@ -14040,6 +14040,9 @@
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
if (admin != null) {
+ if (admin.mCrossProfileCalendarPackages == null) {
+ return true;
+ }
return admin.mCrossProfileCalendarPackages.contains(packageName);
}
}
@@ -14055,7 +14058,7 @@
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
if (admin != null) {
- return new ArrayList<String>(admin.mCrossProfileCalendarPackages);
+ return admin.mCrossProfileCalendarPackages;
}
}
return Collections.emptyList();
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
index 238f077..e99dd4f 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 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.
@@ -21,9 +21,12 @@
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.database.sqlite.SQLiteCursor;
+import android.database.sqlite.SQLiteCursorDriver;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQuery;
import android.net.NetworkUtils;
import android.net.ipmemorystore.NetworkAttributes;
import android.net.ipmemorystore.Status;
@@ -35,6 +38,7 @@
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
+import java.util.StringJoiner;
/**
* Encapsulating class for using the SQLite database backing the memory store.
@@ -46,6 +50,9 @@
*/
public class IpMemoryStoreDatabase {
private static final String TAG = IpMemoryStoreDatabase.class.getSimpleName();
+ // A pair of NetworkAttributes objects is group-close if the confidence that they are
+ // the same is above this cutoff. See NetworkAttributes and SameL3NetworkResponse.
+ private static final float GROUPCLOSE_CONFIDENCE = 0.5f;
/**
* Contract class for the Network Attributes table.
@@ -134,12 +141,14 @@
}
/** Called when the database is created */
+ @Override
public void onCreate(@NonNull final SQLiteDatabase db) {
db.execSQL(NetworkAttributesContract.CREATE_TABLE);
db.execSQL(PrivateDataContract.CREATE_TABLE);
}
/** Called when the database is upgraded */
+ @Override
public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion,
final int newVersion) {
// No upgrade supported yet.
@@ -149,6 +158,7 @@
}
/** Called when the database is downgraded */
+ @Override
public void onDowngrade(@NonNull final SQLiteDatabase db, final int oldVersion,
final int newVersion) {
// Downgrades always nuke all data and recreate an empty table.
@@ -184,30 +194,35 @@
return addresses;
}
+ @NonNull
+ private static ContentValues toContentValues(@Nullable final NetworkAttributes attributes) {
+ final ContentValues values = new ContentValues();
+ if (null == attributes) return values;
+ if (null != attributes.assignedV4Address) {
+ values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
+ NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address));
+ }
+ if (null != attributes.groupHint) {
+ values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
+ }
+ if (null != attributes.dnsAddresses) {
+ values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES,
+ encodeAddressList(attributes.dnsAddresses));
+ }
+ if (null != attributes.mtu) {
+ values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu);
+ }
+ return values;
+ }
+
// Convert a NetworkAttributes object to content values to store them in a table compliant
// with the contract defined in NetworkAttributesContract.
@NonNull
private static ContentValues toContentValues(@NonNull final String key,
@Nullable final NetworkAttributes attributes, final long expiry) {
- final ContentValues values = new ContentValues();
+ final ContentValues values = toContentValues(attributes);
values.put(NetworkAttributesContract.COLNAME_L2KEY, key);
values.put(NetworkAttributesContract.COLNAME_EXPIRYDATE, expiry);
- if (null != attributes) {
- if (null != attributes.assignedV4Address) {
- values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
- NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address));
- }
- if (null != attributes.groupHint) {
- values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
- }
- if (null != attributes.dnsAddresses) {
- values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES,
- encodeAddressList(attributes.dnsAddresses));
- }
- if (null != attributes.mtu) {
- values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu);
- }
- }
return values;
}
@@ -225,6 +240,32 @@
return values;
}
+ @Nullable
+ private static NetworkAttributes readNetworkAttributesLine(@NonNull final Cursor cursor) {
+ // Make sure the data hasn't expired
+ final long expiry = getLong(cursor, NetworkAttributesContract.COLNAME_EXPIRYDATE, -1L);
+ if (expiry < System.currentTimeMillis()) return null;
+
+ final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
+ final int assignedV4AddressInt = getInt(cursor,
+ NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0);
+ final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT);
+ final byte[] dnsAddressesBlob =
+ getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
+ final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1);
+ if (0 != assignedV4AddressInt) {
+ builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt));
+ }
+ builder.setGroupHint(groupHint);
+ if (null != dnsAddressesBlob) {
+ builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob));
+ }
+ if (mtu >= 0) {
+ builder.setMtu(mtu);
+ }
+ return builder.build();
+ }
+
private static final String[] EXPIRY_COLUMN = new String[] {
NetworkAttributesContract.COLNAME_EXPIRYDATE
};
@@ -247,7 +288,9 @@
// result here. 0 results means the key was not found.
if (cursor.getCount() != 1) return EXPIRY_ERROR;
cursor.moveToFirst();
- return cursor.getLong(0); // index in the EXPIRY_COLUMN array
+ final long result = cursor.getLong(0); // index in the EXPIRY_COLUMN array
+ cursor.close();
+ return result;
}
static final int RELEVANCE_ERROR = -1; // Legal values for relevance are positive
@@ -308,30 +351,9 @@
// result here. 0 results means the key was not found.
if (cursor.getCount() != 1) return null;
cursor.moveToFirst();
-
- // Make sure the data hasn't expired
- final long expiry = cursor.getLong(
- cursor.getColumnIndexOrThrow(NetworkAttributesContract.COLNAME_EXPIRYDATE));
- if (expiry < System.currentTimeMillis()) return null;
-
- final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
- final int assignedV4AddressInt = getInt(cursor,
- NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0);
- final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT);
- final byte[] dnsAddressesBlob =
- getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
- final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1);
- if (0 != assignedV4AddressInt) {
- builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt));
- }
- builder.setGroupHint(groupHint);
- if (null != dnsAddressesBlob) {
- builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob));
- }
- if (mtu >= 0) {
- builder.setMtu(mtu);
- }
- return builder.build();
+ final NetworkAttributes attributes = readNetworkAttributesLine(cursor);
+ cursor.close();
+ return attributes;
}
private static final String[] DATA_COLUMN = new String[] {
@@ -353,20 +375,139 @@
// get more than one result here. 0 results means the key was not found.
if (cursor.getCount() != 1) return null;
cursor.moveToFirst();
- return cursor.getBlob(0); // index in the DATA_COLUMN array
+ final byte[] result = cursor.getBlob(0); // index in the DATA_COLUMN array
+ cursor.close();
+ return result;
+ }
+
+ /**
+ * The following is a horrible hack that is necessary because the Android SQLite API does not
+ * have a way to query a binary blob. This, almost certainly, is an overlook.
+ *
+ * The Android SQLite API has two family of methods : one for query that returns data, and
+ * one for more general SQL statements that can execute any statement but may not return
+ * anything. All the query methods, however, take only String[] for the arguments.
+ *
+ * In principle it is simple to write a function that will encode the binary blob in the
+ * way SQLite expects it. However, because the API forces the argument to be coerced into a
+ * String, the SQLiteQuery object generated by the default query methods will bind all
+ * arguments as Strings and SQL will *sanitize* them. This works okay for numeric types,
+ * but the format for blobs is x'<hex string>'. Note the presence of quotes, which will
+ * be sanitized, changing the contents of the field, and the query will fail to match the
+ * blob.
+ *
+ * As far as I can tell, there are two possible ways around this problem. The first one
+ * is to put the data in the query string and eschew it being an argument. This would
+ * require doing the sanitizing by hand. The other is to call bindBlob directly on the
+ * generated SQLiteQuery object, which not only is a lot less dangerous than rolling out
+ * sanitizing, but also will do the right thing if the underlying format ever changes.
+ *
+ * But none of the methods that take an SQLiteQuery object can return data ; this *must*
+ * be called with SQLiteDatabase#query. This object is not accessible from outside.
+ * However, there is a #query version that accepts a CursorFactory and this is pretty
+ * straightforward to implement as all the arguments are coming in and the SQLiteCursor
+ * class is public API.
+ * With this, it's possible to intercept the SQLiteQuery object, and assuming the args
+ * are available, to bind them directly and work around the API's oblivious coercion into
+ * Strings.
+ *
+ * This is really sad, but I don't see another way of having this work than this or the
+ * hand-rolled sanitizing, and this is the lesser evil.
+ */
+ private static class CustomCursorFactory implements SQLiteDatabase.CursorFactory {
+ @NonNull
+ private final ArrayList<Object> mArgs;
+ CustomCursorFactory(@NonNull final ArrayList<Object> args) {
+ mArgs = args;
+ }
+ @Override
+ public Cursor newCursor(final SQLiteDatabase db, final SQLiteCursorDriver masterQuery,
+ final String editTable,
+ final SQLiteQuery query) {
+ int index = 1; // bind is 1-indexed
+ for (final Object arg : mArgs) {
+ if (arg instanceof String) {
+ query.bindString(index++, (String) arg);
+ } else if (arg instanceof Long) {
+ query.bindLong(index++, (Long) arg);
+ } else if (arg instanceof Integer) {
+ query.bindLong(index++, Long.valueOf((Integer) arg));
+ } else if (arg instanceof byte[]) {
+ query.bindBlob(index++, (byte[]) arg);
+ } else {
+ throw new IllegalStateException("Unsupported type CustomCursorFactory "
+ + arg.getClass().toString());
+ }
+ }
+ return new SQLiteCursor(masterQuery, editTable, query);
+ }
+ }
+
+ // Returns the l2key of the closest match, if and only if it matches
+ // closely enough (as determined by group-closeness).
+ @Nullable
+ static String findClosestAttributes(@NonNull final SQLiteDatabase db,
+ @NonNull final NetworkAttributes attr) {
+ if (attr.isEmpty()) return null;
+ final ContentValues values = toContentValues(attr);
+
+ // Build the selection and args. To cut down on the number of lines to search, limit
+ // the search to those with at least one argument equals to the requested attributes.
+ // This works only because null attributes match only will not result in group-closeness.
+ final StringJoiner sj = new StringJoiner(" OR ");
+ final ArrayList<Object> args = new ArrayList<>();
+ args.add(System.currentTimeMillis());
+ for (final String field : values.keySet()) {
+ sj.add(field + " = ?");
+ args.add(values.get(field));
+ }
+
+ final String selection = NetworkAttributesContract.COLNAME_EXPIRYDATE + " > ? AND ("
+ + sj.toString() + ")";
+ final Cursor cursor = db.queryWithFactory(new CustomCursorFactory(args),
+ false, // distinct
+ NetworkAttributesContract.TABLENAME,
+ null, // columns, null means everything
+ selection, // selection
+ null, // selectionArgs, horrendously passed to the cursor factory instead
+ null, // groupBy
+ null, // having
+ null, // orderBy
+ null); // limit
+ if (cursor.getCount() <= 0) return null;
+ cursor.moveToFirst();
+ String bestKey = null;
+ float bestMatchConfidence = GROUPCLOSE_CONFIDENCE; // Never return a match worse than this.
+ while (!cursor.isAfterLast()) {
+ final NetworkAttributes read = readNetworkAttributesLine(cursor);
+ final float confidence = read.getNetworkGroupSamenessConfidence(attr);
+ if (confidence > bestMatchConfidence) {
+ bestKey = getString(cursor, NetworkAttributesContract.COLNAME_L2KEY);
+ bestMatchConfidence = confidence;
+ }
+ cursor.moveToNext();
+ }
+ cursor.close();
+ return bestKey;
}
// Helper methods
- static String getString(final Cursor cursor, final String columnName) {
+ private static String getString(final Cursor cursor, final String columnName) {
final int columnIndex = cursor.getColumnIndex(columnName);
return (columnIndex >= 0) ? cursor.getString(columnIndex) : null;
}
- static byte[] getBlob(final Cursor cursor, final String columnName) {
+ private static byte[] getBlob(final Cursor cursor, final String columnName) {
final int columnIndex = cursor.getColumnIndex(columnName);
return (columnIndex >= 0) ? cursor.getBlob(columnIndex) : null;
}
- static int getInt(final Cursor cursor, final String columnName, final int defaultValue) {
+ private static int getInt(final Cursor cursor, final String columnName,
+ final int defaultValue) {
final int columnIndex = cursor.getColumnIndex(columnName);
return (columnIndex >= 0) ? cursor.getInt(columnIndex) : defaultValue;
}
+ private static long getLong(final Cursor cursor, final String columnName,
+ final long defaultValue) {
+ final int columnIndex = cursor.getColumnIndex(columnName);
+ return (columnIndex >= 0) ? cursor.getLong(columnIndex) : defaultValue;
+ }
}
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
index 444b299..d43dc6a 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
@@ -37,6 +37,7 @@
import android.net.ipmemorystore.IOnStatusListener;
import android.net.ipmemorystore.NetworkAttributes;
import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.SameL3NetworkResponse;
import android.net.ipmemorystore.Status;
import android.net.ipmemorystore.StatusParcelable;
import android.net.ipmemorystore.Utils;
@@ -249,9 +250,26 @@
* Through the listener, returns the L2 key if one matched, or null.
*/
@Override
- public void findL2Key(@NonNull final NetworkAttributesParcelable attributes,
- @NonNull final IOnL2KeyResponseListener listener) {
- // TODO : implement this.
+ public void findL2Key(@Nullable final NetworkAttributesParcelable attributes,
+ @Nullable final IOnL2KeyResponseListener listener) {
+ if (null == listener) return;
+ mExecutor.execute(() -> {
+ try {
+ if (null == attributes) {
+ listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+ return;
+ }
+ if (null == mDb) {
+ listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+ return;
+ }
+ final String key = IpMemoryStoreDatabase.findClosestAttributes(mDb,
+ new NetworkAttributes(attributes));
+ listener.onL2KeyResponse(makeStatus(SUCCESS), key);
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
}
/**
@@ -264,9 +282,40 @@
* Through the listener, a SameL3NetworkResponse containing the answer and confidence.
*/
@Override
- public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
- @NonNull final IOnSameNetworkResponseListener listener) {
- // TODO : implement this.
+ public void isSameNetwork(@Nullable final String l2Key1, @Nullable final String l2Key2,
+ @Nullable final IOnSameNetworkResponseListener listener) {
+ if (null == listener) return;
+ mExecutor.execute(() -> {
+ try {
+ if (null == l2Key1 || null == l2Key2) {
+ listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+ return;
+ }
+ if (null == mDb) {
+ listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+ return;
+ }
+ try {
+ final NetworkAttributes attr1 =
+ IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key1);
+ final NetworkAttributes attr2 =
+ IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key2);
+ if (null == attr1 || null == attr2) {
+ listener.onSameNetworkResponse(makeStatus(SUCCESS),
+ new SameL3NetworkResponse(l2Key1, l2Key2,
+ -1f /* never connected */).toParcelable());
+ return;
+ }
+ final float confidence = attr1.getNetworkGroupSamenessConfidence(attr2);
+ listener.onSameNetworkResponse(makeStatus(SUCCESS),
+ new SameL3NetworkResponse(l2Key1, l2Key2, confidence).toParcelable());
+ } catch (Exception e) {
+ listener.onSameNetworkResponse(makeStatus(ERROR_GENERIC), null);
+ }
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
}
/**
@@ -285,21 +334,22 @@
mExecutor.execute(() -> {
try {
if (null == l2Key) {
- listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, null);
+ listener.onNetworkAttributesRetrieved(
+ makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, null);
return;
}
if (null == mDb) {
- listener.onL2KeyResponse(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key,
- null);
+ listener.onNetworkAttributesRetrieved(
+ makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key, null);
return;
}
try {
final NetworkAttributes attributes =
IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key);
- listener.onL2KeyResponse(makeStatus(SUCCESS), l2Key,
+ listener.onNetworkAttributesRetrieved(makeStatus(SUCCESS), l2Key,
null == attributes ? null : attributes.toParcelable());
} catch (final Exception e) {
- listener.onL2KeyResponse(makeStatus(ERROR_GENERIC), l2Key, null);
+ listener.onNetworkAttributesRetrieved(makeStatus(ERROR_GENERIC), l2Key, null);
}
} catch (final RemoteException e) {
// Client at the other end died
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index cef47ca..65eaf554 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -136,6 +136,7 @@
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.telecom.TelecomLoaderService;
+import com.android.server.testharness.TestHarnessModeService;
import com.android.server.textclassifier.TextClassificationManagerService;
import com.android.server.textservices.TextServicesManagerService;
import com.android.server.trust.TrustManagerService;
@@ -327,6 +328,11 @@
private static native void startHidlServices();
/**
+ * Mark this process' heap as profileable. Only for debug builds.
+ */
+ private static native void initZygoteChildHeapProfiling();
+
+ /**
* The main entry point from zygote.
*/
public static void main(String[] args) {
@@ -448,6 +454,11 @@
// Initialize native services.
System.loadLibrary("android_servers");
+ // Debug builds - allow heap profiling.
+ if (Build.IS_DEBUGGABLE) {
+ initZygoteChildHeapProfiling();
+ }
+
// Check whether we failed to shut down last time we tried.
// This call may not return.
performPendingShutdown();
@@ -862,7 +873,7 @@
TimingsTraceLog traceLog = new TimingsTraceLog(
SYSTEM_SERVER_TIMING_ASYNC_TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
traceLog.traceBegin(SECONDARY_ZYGOTE_PRELOAD);
- if (!Process.zygoteProcess.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) {
+ if (!Process.ZYGOTE_PROCESS.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) {
Slog.e(TAG, "Unable to preload default resources");
}
traceLog.traceEnd();
@@ -1147,6 +1158,10 @@
traceBeginAndSlog("StartPersistentDataBlock");
mSystemServiceManager.startService(PersistentDataBlockService.class);
traceEnd();
+
+ traceBeginAndSlog("StartTestHarnessMode");
+ mSystemServiceManager.startService(TestHarnessModeService.class);
+ traceEnd();
}
if (hasPdb || OemLockService.isHalPresent()) {
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 3b4d6a7..30c7de5 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -3,18 +3,18 @@
srcs: ["java/**/*.java"],
}
-// TODO: move to networking module with DhcpClient and remove lib
-java_library {
- name: "dhcp-packet-lib",
- srcs: [
- "java/android/net/dhcp/*Packet.java",
- ]
-}
-
filegroup {
name: "services-networkstack-shared-srcs",
srcs: [
- "java/android/net/util/FdEventsReader.java", // TODO: move to NetworkStack with IpClient
+ "java/android/net/ip/InterfaceController.java", // TODO: move to NetworkStack with tethering
+ "java/android/net/util/InterfaceParams.java", // TODO: move to NetworkStack with IpServer
"java/android/net/shared/*.java",
+ ],
+}
+
+java_library {
+ name: "services-netlink-lib",
+ srcs: [
+ "java/android/net/netlink/*.java",
]
}
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 15acc0e..cddb91f 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 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.
@@ -16,1037 +16,14 @@
package android.net.dhcp;
-import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS;
-import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER;
-import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME;
-import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME;
-import static android.net.dhcp.DhcpPacket.DHCP_MTU;
-import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME;
-import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME;
-import static android.net.dhcp.DhcpPacket.DHCP_ROUTER;
-import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK;
-import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO;
-import static android.net.dhcp.DhcpPacket.INADDR_ANY;
-import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.AF_PACKET;
-import static android.system.OsConstants.ETH_P_IP;
-import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.SOCK_DGRAM;
-import static android.system.OsConstants.SOCK_RAW;
-import static android.system.OsConstants.SOL_SOCKET;
-import static android.system.OsConstants.SO_BINDTODEVICE;
-import static android.system.OsConstants.SO_BROADCAST;
-import static android.system.OsConstants.SO_RCVBUF;
-import static android.system.OsConstants.SO_REUSEADDR;
-
-import android.content.Context;
-import android.net.DhcpResults;
-import android.net.NetworkUtils;
-import android.net.TrafficStats;
-import android.net.metrics.DhcpClientEvent;
-import android.net.metrics.DhcpErrorEvent;
-import android.net.metrics.IpConnectivityLog;
-import android.net.util.InterfaceParams;
-import android.os.Message;
-import android.os.SystemClock;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.PacketSocketAddress;
-import android.util.EventLog;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.TimeUtils;
-
-import com.android.internal.util.HexDump;
-import com.android.internal.util.MessageUtils;
-import com.android.internal.util.Protocol;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.internal.util.WakeupMessage;
-
-import libcore.io.IoBridge;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.Inet4Address;
-import java.net.SocketException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.Random;
-
/**
- * A DHCPv4 client.
- *
- * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android
- * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine.
- *
- * TODO:
- *
- * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour).
- * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not
- * do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a
- * given SSID), it requests the last-leased IP address on the same interface, causing a delay if
- * the server NAKs or a timeout if it doesn't.
- *
- * Known differences from current behaviour:
- *
- * - Does not request the "static routes" option.
- * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now.
- * - Requests the "broadcast" option, but does nothing with it.
- * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0).
- *
- * @hide
+ * TODO: remove this class after migrating clients.
*/
-public class DhcpClient extends StateMachine {
+public class DhcpClient {
+ public static final int CMD_PRE_DHCP_ACTION = 1003;
+ public static final int CMD_POST_DHCP_ACTION = 1004;
+ public static final int CMD_PRE_DHCP_ACTION_COMPLETE = 1006;
- private static final String TAG = "DhcpClient";
- private static final boolean DBG = true;
- private static final boolean STATE_DBG = false;
- private static final boolean MSG_DBG = false;
- private static final boolean PACKET_DBG = false;
-
- // Timers and timeouts.
- private static final int SECONDS = 1000;
- private static final int FIRST_TIMEOUT_MS = 2 * SECONDS;
- private static final int MAX_TIMEOUT_MS = 128 * SECONDS;
-
- // This is not strictly needed, since the client is asynchronous and implements exponential
- // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was
- // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at
- // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter.
- private static final int DHCP_TIMEOUT_MS = 36 * SECONDS;
-
- private static final int PUBLIC_BASE = Protocol.BASE_DHCP;
-
- /* Commands from controller to start/stop DHCP */
- public static final int CMD_START_DHCP = PUBLIC_BASE + 1;
- public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2;
-
- /* Notification from DHCP state machine prior to DHCP discovery/renewal */
- public static final int CMD_PRE_DHCP_ACTION = PUBLIC_BASE + 3;
- /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
- * success/failure */
- public static final int CMD_POST_DHCP_ACTION = PUBLIC_BASE + 4;
- /* Notification from DHCP state machine before quitting */
- public static final int CMD_ON_QUIT = PUBLIC_BASE + 5;
-
- /* Command from controller to indicate DHCP discovery/renewal can continue
- * after pre DHCP action is complete */
- public static final int CMD_PRE_DHCP_ACTION_COMPLETE = PUBLIC_BASE + 6;
-
- /* Command and event notification to/from IpManager requesting the setting
- * (or clearing) of an IPv4 LinkAddress.
- */
- public static final int CMD_CLEAR_LINKADDRESS = PUBLIC_BASE + 7;
- public static final int CMD_CONFIGURE_LINKADDRESS = PUBLIC_BASE + 8;
- public static final int EVENT_LINKADDRESS_CONFIGURED = PUBLIC_BASE + 9;
-
- /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */
public static final int DHCP_SUCCESS = 1;
public static final int DHCP_FAILURE = 2;
-
- // Internal messages.
- private static final int PRIVATE_BASE = Protocol.BASE_DHCP + 100;
- private static final int CMD_KICK = PRIVATE_BASE + 1;
- private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 2;
- private static final int CMD_TIMEOUT = PRIVATE_BASE + 3;
- private static final int CMD_RENEW_DHCP = PRIVATE_BASE + 4;
- private static final int CMD_REBIND_DHCP = PRIVATE_BASE + 5;
- private static final int CMD_EXPIRE_DHCP = PRIVATE_BASE + 6;
-
- // For message logging.
- private static final Class[] sMessageClasses = { DhcpClient.class };
- private static final SparseArray<String> sMessageNames =
- MessageUtils.findMessageNames(sMessageClasses);
-
- // DHCP parameters that we request.
- /* package */ static final byte[] REQUESTED_PARAMS = new byte[] {
- DHCP_SUBNET_MASK,
- DHCP_ROUTER,
- DHCP_DNS_SERVER,
- DHCP_DOMAIN_NAME,
- DHCP_MTU,
- DHCP_BROADCAST_ADDRESS, // TODO: currently ignored.
- DHCP_LEASE_TIME,
- DHCP_RENEWAL_TIME,
- DHCP_REBINDING_TIME,
- DHCP_VENDOR_INFO,
- };
-
- // DHCP flag that means "yes, we support unicast."
- private static final boolean DO_UNICAST = false;
-
- // System services / libraries we use.
- private final Context mContext;
- private final Random mRandom;
- private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
-
- // Sockets.
- // - We use a packet socket to receive, because servers send us packets bound for IP addresses
- // which we have not yet configured, and the kernel protocol stack drops these.
- // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
- // be off-link as well as on-link).
- private FileDescriptor mPacketSock;
- private FileDescriptor mUdpSock;
- private ReceiveThread mReceiveThread;
-
- // State variables.
- private final StateMachine mController;
- private final WakeupMessage mKickAlarm;
- private final WakeupMessage mTimeoutAlarm;
- private final WakeupMessage mRenewAlarm;
- private final WakeupMessage mRebindAlarm;
- private final WakeupMessage mExpiryAlarm;
- private final String mIfaceName;
-
- private boolean mRegisteredForPreDhcpNotification;
- private InterfaceParams mIface;
- // TODO: MacAddress-ify more of this class hierarchy.
- private byte[] mHwAddr;
- private PacketSocketAddress mInterfaceBroadcastAddr;
- private int mTransactionId;
- private long mTransactionStartMillis;
- private DhcpResults mDhcpLease;
- private long mDhcpLeaseExpiry;
- private DhcpResults mOffer;
-
- // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState.
- private long mLastInitEnterTime;
- private long mLastBoundExitTime;
-
- // States.
- private State mStoppedState = new StoppedState();
- private State mDhcpState = new DhcpState();
- private State mDhcpInitState = new DhcpInitState();
- private State mDhcpSelectingState = new DhcpSelectingState();
- private State mDhcpRequestingState = new DhcpRequestingState();
- private State mDhcpHaveLeaseState = new DhcpHaveLeaseState();
- private State mConfiguringInterfaceState = new ConfiguringInterfaceState();
- private State mDhcpBoundState = new DhcpBoundState();
- private State mDhcpRenewingState = new DhcpRenewingState();
- private State mDhcpRebindingState = new DhcpRebindingState();
- private State mDhcpInitRebootState = new DhcpInitRebootState();
- private State mDhcpRebootingState = new DhcpRebootingState();
- private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState);
- private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState);
-
- private WakeupMessage makeWakeupMessage(String cmdName, int cmd) {
- cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName;
- return new WakeupMessage(mContext, getHandler(), cmdName, cmd);
- }
-
- // TODO: Take an InterfaceParams instance instead of an interface name String.
- private DhcpClient(Context context, StateMachine controller, String iface) {
- super(TAG, controller.getHandler());
-
- mContext = context;
- mController = controller;
- mIfaceName = iface;
-
- addState(mStoppedState);
- addState(mDhcpState);
- addState(mDhcpInitState, mDhcpState);
- addState(mWaitBeforeStartState, mDhcpState);
- addState(mDhcpSelectingState, mDhcpState);
- addState(mDhcpRequestingState, mDhcpState);
- addState(mDhcpHaveLeaseState, mDhcpState);
- addState(mConfiguringInterfaceState, mDhcpHaveLeaseState);
- addState(mDhcpBoundState, mDhcpHaveLeaseState);
- addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState);
- addState(mDhcpRenewingState, mDhcpHaveLeaseState);
- addState(mDhcpRebindingState, mDhcpHaveLeaseState);
- addState(mDhcpInitRebootState, mDhcpState);
- addState(mDhcpRebootingState, mDhcpState);
-
- setInitialState(mStoppedState);
-
- mRandom = new Random();
-
- // Used to schedule packet retransmissions.
- mKickAlarm = makeWakeupMessage("KICK", CMD_KICK);
- // Used to time out PacketRetransmittingStates.
- mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT);
- // Used to schedule DHCP reacquisition.
- mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP);
- mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP);
- mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP);
- }
-
- public void registerForPreDhcpNotification() {
- mRegisteredForPreDhcpNotification = true;
- }
-
- public static DhcpClient makeDhcpClient(
- Context context, StateMachine controller, InterfaceParams ifParams) {
- DhcpClient client = new DhcpClient(context, controller, ifParams.name);
- client.mIface = ifParams;
- client.start();
- return client;
- }
-
- private boolean initInterface() {
- if (mIface == null) mIface = InterfaceParams.getByName(mIfaceName);
- if (mIface == null) {
- Log.e(TAG, "Can't determine InterfaceParams for " + mIfaceName);
- return false;
- }
-
- mHwAddr = mIface.macAddr.toByteArray();
- mInterfaceBroadcastAddr = new PacketSocketAddress(mIface.index, DhcpPacket.ETHER_BROADCAST);
- return true;
- }
-
- private void startNewTransaction() {
- mTransactionId = mRandom.nextInt();
- mTransactionStartMillis = SystemClock.elapsedRealtime();
- }
-
- private boolean initSockets() {
- return initPacketSocket() && initUdpSocket();
- }
-
- private boolean initPacketSocket() {
- try {
- mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP);
- PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IP, mIface.index);
- Os.bind(mPacketSock, addr);
- NetworkUtils.attachDhcpFilter(mPacketSock);
- } catch(SocketException|ErrnoException e) {
- Log.e(TAG, "Error creating packet socket", e);
- return false;
- }
- return true;
- }
-
- private boolean initUdpSocket() {
- final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_DHCP);
- try {
- mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
- Os.setsockoptIfreq(mUdpSock, SOL_SOCKET, SO_BINDTODEVICE, mIfaceName);
- Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
- Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0);
- Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT);
- NetworkUtils.protectFromVpn(mUdpSock);
- } catch(SocketException|ErrnoException e) {
- Log.e(TAG, "Error creating UDP socket", e);
- return false;
- } finally {
- TrafficStats.setThreadStatsTag(oldTag);
- }
- return true;
- }
-
- private boolean connectUdpSock(Inet4Address to) {
- try {
- Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER);
- return true;
- } catch (SocketException|ErrnoException e) {
- Log.e(TAG, "Error connecting UDP socket", e);
- return false;
- }
- }
-
- private static void closeQuietly(FileDescriptor fd) {
- try {
- IoBridge.closeAndSignalBlockedThreads(fd);
- } catch (IOException ignored) {}
- }
-
- private void closeSockets() {
- closeQuietly(mUdpSock);
- closeQuietly(mPacketSock);
- }
-
- class ReceiveThread extends Thread {
-
- private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH];
- private volatile boolean mStopped = false;
-
- public void halt() {
- mStopped = true;
- closeSockets(); // Interrupts the read() call the thread is blocked in.
- }
-
- @Override
- public void run() {
- if (DBG) Log.d(TAG, "Receive thread started");
- while (!mStopped) {
- int length = 0; // Or compiler can't tell it's initialized if a parse error occurs.
- try {
- length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
- DhcpPacket packet = null;
- packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
- if (DBG) Log.d(TAG, "Received packet: " + packet);
- sendMessage(CMD_RECEIVED_PACKET, packet);
- } catch (IOException|ErrnoException e) {
- if (!mStopped) {
- Log.e(TAG, "Read error", e);
- logError(DhcpErrorEvent.RECEIVE_ERROR);
- }
- } catch (DhcpPacket.ParseException e) {
- Log.e(TAG, "Can't parse packet: " + e.getMessage());
- if (PACKET_DBG) {
- Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length));
- }
- if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) {
- int snetTagId = 0x534e4554;
- String bugId = "31850211";
- int uid = -1;
- String data = DhcpPacket.ParseException.class.getName();
- EventLog.writeEvent(snetTagId, bugId, uid, data);
- }
- logError(e.errorCode);
- }
- }
- if (DBG) Log.d(TAG, "Receive thread stopped");
- }
- }
-
- private short getSecs() {
- return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000);
- }
-
- private boolean transmitPacket(ByteBuffer buf, String description, int encap, Inet4Address to) {
- try {
- if (encap == DhcpPacket.ENCAP_L2) {
- if (DBG) Log.d(TAG, "Broadcasting " + description);
- Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
- } else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) {
- if (DBG) Log.d(TAG, "Broadcasting " + description);
- // We only send L3-encapped broadcasts in DhcpRebindingState,
- // where we have an IP address and an unconnected UDP socket.
- //
- // N.B.: We only need this codepath because DhcpRequestPacket
- // hardcodes the source IP address to 0.0.0.0. We could reuse
- // the packet socket if this ever changes.
- Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER);
- } else {
- // It's safe to call getpeername here, because we only send unicast packets if we
- // have an IP address, and we connect the UDP socket in DhcpBoundState#enter.
- if (DBG) Log.d(TAG, String.format("Unicasting %s to %s",
- description, Os.getpeername(mUdpSock)));
- Os.write(mUdpSock, buf);
- }
- } catch(ErrnoException|IOException e) {
- Log.e(TAG, "Can't send packet: ", e);
- return false;
- }
- return true;
- }
-
- private boolean sendDiscoverPacket() {
- ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
- DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
- DO_UNICAST, REQUESTED_PARAMS);
- return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
- }
-
- private boolean sendRequestPacket(
- Inet4Address clientAddress, Inet4Address requestedAddress,
- Inet4Address serverAddress, Inet4Address to) {
- // TODO: should we use the transaction ID from the server?
- final int encap = INADDR_ANY.equals(clientAddress)
- ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP;
-
- ByteBuffer packet = DhcpPacket.buildRequestPacket(
- encap, mTransactionId, getSecs(), clientAddress,
- DO_UNICAST, mHwAddr, requestedAddress,
- serverAddress, REQUESTED_PARAMS, null);
- String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null;
- String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
- " request=" + requestedAddress.getHostAddress() +
- " serverid=" + serverStr;
- return transmitPacket(packet, description, encap, to);
- }
-
- private void scheduleLeaseTimers() {
- if (mDhcpLeaseExpiry == 0) {
- Log.d(TAG, "Infinite lease, no timer scheduling needed");
- return;
- }
-
- final long now = SystemClock.elapsedRealtime();
-
- // TODO: consider getting the renew and rebind timers from T1 and T2.
- // See also:
- // https://tools.ietf.org/html/rfc2131#section-4.4.5
- // https://tools.ietf.org/html/rfc1533#section-9.9
- // https://tools.ietf.org/html/rfc1533#section-9.10
- final long remainingDelay = mDhcpLeaseExpiry - now;
- final long renewDelay = remainingDelay / 2;
- final long rebindDelay = remainingDelay * 7 / 8;
- mRenewAlarm.schedule(now + renewDelay);
- mRebindAlarm.schedule(now + rebindDelay);
- mExpiryAlarm.schedule(now + remainingDelay);
- Log.d(TAG, "Scheduling renewal in " + (renewDelay / 1000) + "s");
- Log.d(TAG, "Scheduling rebind in " + (rebindDelay / 1000) + "s");
- Log.d(TAG, "Scheduling expiry in " + (remainingDelay / 1000) + "s");
- }
-
- private void notifySuccess() {
- mController.sendMessage(
- CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease));
- }
-
- private void notifyFailure() {
- mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0, null);
- }
-
- private void acceptDhcpResults(DhcpResults results, String msg) {
- mDhcpLease = results;
- mOffer = null;
- Log.d(TAG, msg + " lease: " + mDhcpLease);
- notifySuccess();
- }
-
- private void clearDhcpState() {
- mDhcpLease = null;
- mDhcpLeaseExpiry = 0;
- mOffer = null;
- }
-
- /**
- * Quit the DhcpStateMachine.
- *
- * @hide
- */
- public void doQuit() {
- Log.d(TAG, "doQuit");
- quit();
- }
-
- @Override
- protected void onQuitting() {
- Log.d(TAG, "onQuitting");
- mController.sendMessage(CMD_ON_QUIT);
- }
-
- abstract class LoggingState extends State {
- private long mEnterTimeMs;
-
- @Override
- public void enter() {
- if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
- mEnterTimeMs = SystemClock.elapsedRealtime();
- }
-
- @Override
- public void exit() {
- long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs;
- logState(getName(), (int) durationMs);
- }
-
- private String messageName(int what) {
- return sMessageNames.get(what, Integer.toString(what));
- }
-
- private String messageToString(Message message) {
- long now = SystemClock.uptimeMillis();
- StringBuilder b = new StringBuilder(" ");
- TimeUtils.formatDuration(message.getWhen() - now, b);
- b.append(" ").append(messageName(message.what))
- .append(" ").append(message.arg1)
- .append(" ").append(message.arg2)
- .append(" ").append(message.obj);
- return b.toString();
- }
-
- @Override
- public boolean processMessage(Message message) {
- if (MSG_DBG) {
- Log.d(TAG, getName() + messageToString(message));
- }
- return NOT_HANDLED;
- }
-
- @Override
- public String getName() {
- // All DhcpClient's states are inner classes with a well defined name.
- // Use getSimpleName() and avoid super's getName() creating new String instances.
- return getClass().getSimpleName();
- }
- }
-
- // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with
- // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState.
- abstract class WaitBeforeOtherState extends LoggingState {
- protected State mOtherState;
-
- @Override
- public void enter() {
- super.enter();
- mController.sendMessage(CMD_PRE_DHCP_ACTION);
- }
-
- @Override
- public boolean processMessage(Message message) {
- super.processMessage(message);
- switch (message.what) {
- case CMD_PRE_DHCP_ACTION_COMPLETE:
- transitionTo(mOtherState);
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
- }
-
- class StoppedState extends State {
- @Override
- public boolean processMessage(Message message) {
- switch (message.what) {
- case CMD_START_DHCP:
- if (mRegisteredForPreDhcpNotification) {
- transitionTo(mWaitBeforeStartState);
- } else {
- transitionTo(mDhcpInitState);
- }
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
- }
-
- class WaitBeforeStartState extends WaitBeforeOtherState {
- public WaitBeforeStartState(State otherState) {
- super();
- mOtherState = otherState;
- }
- }
-
- class WaitBeforeRenewalState extends WaitBeforeOtherState {
- public WaitBeforeRenewalState(State otherState) {
- super();
- mOtherState = otherState;
- }
- }
-
- class DhcpState extends State {
- @Override
- public void enter() {
- clearDhcpState();
- if (initInterface() && initSockets()) {
- mReceiveThread = new ReceiveThread();
- mReceiveThread.start();
- } else {
- notifyFailure();
- transitionTo(mStoppedState);
- }
- }
-
- @Override
- public void exit() {
- if (mReceiveThread != null) {
- mReceiveThread.halt(); // Also closes sockets.
- mReceiveThread = null;
- }
- clearDhcpState();
- }
-
- @Override
- public boolean processMessage(Message message) {
- super.processMessage(message);
- switch (message.what) {
- case CMD_STOP_DHCP:
- transitionTo(mStoppedState);
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
- }
-
- public boolean isValidPacket(DhcpPacket packet) {
- // TODO: check checksum.
- int xid = packet.getTransactionId();
- if (xid != mTransactionId) {
- Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId);
- return false;
- }
- if (!Arrays.equals(packet.getClientMac(), mHwAddr)) {
- Log.d(TAG, "MAC addr mismatch: got " +
- HexDump.toHexString(packet.getClientMac()) + ", expected " +
- HexDump.toHexString(packet.getClientMac()));
- return false;
- }
- return true;
- }
-
- public void setDhcpLeaseExpiry(DhcpPacket packet) {
- long leaseTimeMillis = packet.getLeaseTimeMillis();
- mDhcpLeaseExpiry =
- (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0;
- }
-
- /**
- * Retransmits packets using jittered exponential backoff with an optional timeout. Packet
- * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass
- * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout
- * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the
- * state.
- *
- * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a
- * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET
- * sent by the receive thread. They may also set mTimeout and implement timeout.
- */
- abstract class PacketRetransmittingState extends LoggingState {
-
- private int mTimer;
- protected int mTimeout = 0;
-
- @Override
- public void enter() {
- super.enter();
- initTimer();
- maybeInitTimeout();
- sendMessage(CMD_KICK);
- }
-
- @Override
- public boolean processMessage(Message message) {
- super.processMessage(message);
- switch (message.what) {
- case CMD_KICK:
- sendPacket();
- scheduleKick();
- return HANDLED;
- case CMD_RECEIVED_PACKET:
- receivePacket((DhcpPacket) message.obj);
- return HANDLED;
- case CMD_TIMEOUT:
- timeout();
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
-
- @Override
- public void exit() {
- super.exit();
- mKickAlarm.cancel();
- mTimeoutAlarm.cancel();
- }
-
- abstract protected boolean sendPacket();
- abstract protected void receivePacket(DhcpPacket packet);
- protected void timeout() {}
-
- protected void initTimer() {
- mTimer = FIRST_TIMEOUT_MS;
- }
-
- protected int jitterTimer(int baseTimer) {
- int maxJitter = baseTimer / 10;
- int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter;
- return baseTimer + jitter;
- }
-
- protected void scheduleKick() {
- long now = SystemClock.elapsedRealtime();
- long timeout = jitterTimer(mTimer);
- long alarmTime = now + timeout;
- mKickAlarm.schedule(alarmTime);
- mTimer *= 2;
- if (mTimer > MAX_TIMEOUT_MS) {
- mTimer = MAX_TIMEOUT_MS;
- }
- }
-
- protected void maybeInitTimeout() {
- if (mTimeout > 0) {
- long alarmTime = SystemClock.elapsedRealtime() + mTimeout;
- mTimeoutAlarm.schedule(alarmTime);
- }
- }
- }
-
- class DhcpInitState extends PacketRetransmittingState {
- public DhcpInitState() {
- super();
- }
-
- @Override
- public void enter() {
- super.enter();
- startNewTransaction();
- mLastInitEnterTime = SystemClock.elapsedRealtime();
- }
-
- protected boolean sendPacket() {
- return sendDiscoverPacket();
- }
-
- protected void receivePacket(DhcpPacket packet) {
- if (!isValidPacket(packet)) return;
- if (!(packet instanceof DhcpOfferPacket)) return;
- mOffer = packet.toDhcpResults();
- if (mOffer != null) {
- Log.d(TAG, "Got pending lease: " + mOffer);
- transitionTo(mDhcpRequestingState);
- }
- }
- }
-
- // Not implemented. We request the first offer we receive.
- class DhcpSelectingState extends LoggingState {
- }
-
- class DhcpRequestingState extends PacketRetransmittingState {
- public DhcpRequestingState() {
- mTimeout = DHCP_TIMEOUT_MS / 2;
- }
-
- protected boolean sendPacket() {
- return sendRequestPacket(
- INADDR_ANY, // ciaddr
- (Inet4Address) mOffer.ipAddress.getAddress(), // DHCP_REQUESTED_IP
- (Inet4Address) mOffer.serverAddress, // DHCP_SERVER_IDENTIFIER
- INADDR_BROADCAST); // packet destination address
- }
-
- protected void receivePacket(DhcpPacket packet) {
- if (!isValidPacket(packet)) return;
- if ((packet instanceof DhcpAckPacket)) {
- DhcpResults results = packet.toDhcpResults();
- if (results != null) {
- setDhcpLeaseExpiry(packet);
- acceptDhcpResults(results, "Confirmed");
- transitionTo(mConfiguringInterfaceState);
- }
- } else if (packet instanceof DhcpNakPacket) {
- // TODO: Wait a while before returning into INIT state.
- Log.d(TAG, "Received NAK, returning to INIT");
- mOffer = null;
- transitionTo(mDhcpInitState);
- }
- }
-
- @Override
- protected void timeout() {
- // After sending REQUESTs unsuccessfully for a while, go back to init.
- transitionTo(mDhcpInitState);
- }
- }
-
- class DhcpHaveLeaseState extends State {
- @Override
- public boolean processMessage(Message message) {
- switch (message.what) {
- case CMD_EXPIRE_DHCP:
- Log.d(TAG, "Lease expired!");
- notifyFailure();
- transitionTo(mDhcpInitState);
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
-
- @Override
- public void exit() {
- // Clear any extant alarms.
- mRenewAlarm.cancel();
- mRebindAlarm.cancel();
- mExpiryAlarm.cancel();
- clearDhcpState();
- // Tell IpManager to clear the IPv4 address. There is no need to
- // wait for confirmation since any subsequent packets are sent from
- // INADDR_ANY anyway (DISCOVER, REQUEST).
- mController.sendMessage(CMD_CLEAR_LINKADDRESS);
- }
- }
-
- class ConfiguringInterfaceState extends LoggingState {
- @Override
- public void enter() {
- super.enter();
- mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress);
- }
-
- @Override
- public boolean processMessage(Message message) {
- super.processMessage(message);
- switch (message.what) {
- case EVENT_LINKADDRESS_CONFIGURED:
- transitionTo(mDhcpBoundState);
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
- }
-
- class DhcpBoundState extends LoggingState {
- @Override
- public void enter() {
- super.enter();
- if (mDhcpLease.serverAddress != null && !connectUdpSock(mDhcpLease.serverAddress)) {
- // There's likely no point in going into DhcpInitState here, we'll probably
- // just repeat the transaction, get the same IP address as before, and fail.
- //
- // NOTE: It is observed that connectUdpSock() basically never fails, due to
- // SO_BINDTODEVICE. Examining the local socket address shows it will happily
- // return an IPv4 address from another interface, or even return "0.0.0.0".
- //
- // TODO: Consider deleting this check, following testing on several kernels.
- notifyFailure();
- transitionTo(mStoppedState);
- }
-
- scheduleLeaseTimers();
- logTimeToBoundState();
- }
-
- @Override
- public void exit() {
- super.exit();
- mLastBoundExitTime = SystemClock.elapsedRealtime();
- }
-
- @Override
- public boolean processMessage(Message message) {
- super.processMessage(message);
- switch (message.what) {
- case CMD_RENEW_DHCP:
- if (mRegisteredForPreDhcpNotification) {
- transitionTo(mWaitBeforeRenewalState);
- } else {
- transitionTo(mDhcpRenewingState);
- }
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
-
- private void logTimeToBoundState() {
- long now = SystemClock.elapsedRealtime();
- if (mLastBoundExitTime > mLastInitEnterTime) {
- logState(DhcpClientEvent.RENEWING_BOUND, (int)(now - mLastBoundExitTime));
- } else {
- logState(DhcpClientEvent.INITIAL_BOUND, (int)(now - mLastInitEnterTime));
- }
- }
- }
-
- abstract class DhcpReacquiringState extends PacketRetransmittingState {
- protected String mLeaseMsg;
-
- @Override
- public void enter() {
- super.enter();
- startNewTransaction();
- }
-
- abstract protected Inet4Address packetDestination();
-
- protected boolean sendPacket() {
- return sendRequestPacket(
- (Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr
- INADDR_ANY, // DHCP_REQUESTED_IP
- null, // DHCP_SERVER_IDENTIFIER
- packetDestination()); // packet destination address
- }
-
- protected void receivePacket(DhcpPacket packet) {
- if (!isValidPacket(packet)) return;
- if ((packet instanceof DhcpAckPacket)) {
- final DhcpResults results = packet.toDhcpResults();
- if (results != null) {
- if (!mDhcpLease.ipAddress.equals(results.ipAddress)) {
- Log.d(TAG, "Renewed lease not for our current IP address!");
- notifyFailure();
- transitionTo(mDhcpInitState);
- }
- setDhcpLeaseExpiry(packet);
- // Updating our notion of DhcpResults here only causes the
- // DNS servers and routes to be updated in LinkProperties
- // in IpManager and by any overridden relevant handlers of
- // the registered IpManager.Callback. IP address changes
- // are not supported here.
- acceptDhcpResults(results, mLeaseMsg);
- transitionTo(mDhcpBoundState);
- }
- } else if (packet instanceof DhcpNakPacket) {
- Log.d(TAG, "Received NAK, returning to INIT");
- notifyFailure();
- transitionTo(mDhcpInitState);
- }
- }
- }
-
- class DhcpRenewingState extends DhcpReacquiringState {
- public DhcpRenewingState() {
- mLeaseMsg = "Renewed";
- }
-
- @Override
- public boolean processMessage(Message message) {
- if (super.processMessage(message) == HANDLED) {
- return HANDLED;
- }
-
- switch (message.what) {
- case CMD_REBIND_DHCP:
- transitionTo(mDhcpRebindingState);
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
-
- @Override
- protected Inet4Address packetDestination() {
- // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but...
- // http://b/25343517 . Try to make things work anyway by using broadcast renews.
- return (mDhcpLease.serverAddress != null) ?
- mDhcpLease.serverAddress : INADDR_BROADCAST;
- }
- }
-
- class DhcpRebindingState extends DhcpReacquiringState {
- public DhcpRebindingState() {
- mLeaseMsg = "Rebound";
- }
-
- @Override
- public void enter() {
- super.enter();
-
- // We need to broadcast and possibly reconnect the socket to a
- // completely different server.
- closeQuietly(mUdpSock);
- if (!initUdpSocket()) {
- Log.e(TAG, "Failed to recreate UDP socket");
- transitionTo(mDhcpInitState);
- }
- }
-
- @Override
- protected Inet4Address packetDestination() {
- return INADDR_BROADCAST;
- }
- }
-
- class DhcpInitRebootState extends LoggingState {
- }
-
- class DhcpRebootingState extends LoggingState {
- }
-
- private void logError(int errorCode) {
- mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode));
- }
-
- private void logState(String name, int durationMs) {
- final DhcpClientEvent event = new DhcpClientEvent.Builder()
- .setMsg(name)
- .setDurationMs(durationMs)
- .build();
- mMetricsLog.log(mIfaceName, event);
- }
}
diff --git a/services/net/java/android/net/ip/InterfaceController.java b/services/net/java/android/net/ip/InterfaceController.java
index 55dfcef..b3af67c 100644
--- a/services/net/java/android/net/ip/InterfaceController.java
+++ b/services/net/java/android/net/ip/InterfaceController.java
@@ -18,13 +18,14 @@
import android.net.INetd;
import android.net.InterfaceConfiguration;
+import android.net.InterfaceConfigurationParcel;
import android.net.LinkAddress;
import android.net.util.SharedLog;
-import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.system.OsConstants;
+import java.net.Inet4Address;
import java.net.InetAddress;
@@ -39,76 +40,96 @@
private final static boolean DBG = false;
private final String mIfName;
- private final INetworkManagementService mNMS;
private final INetd mNetd;
private final SharedLog mLog;
- public InterfaceController(String ifname, INetworkManagementService nms, INetd netd,
- SharedLog log) {
+ public InterfaceController(String ifname, INetd netd, SharedLog log) {
mIfName = ifname;
- mNMS = nms;
mNetd = netd;
mLog = log;
}
+ private boolean setInterfaceConfig(InterfaceConfiguration config) {
+ final InterfaceConfigurationParcel cfgParcel = config.toParcel(mIfName);
+
+ try {
+ mNetd.interfaceSetCfg(cfgParcel);
+ } catch (RemoteException | ServiceSpecificException e) {
+ logError("Setting IPv4 address to %s/%d failed: %s",
+ cfgParcel.ipv4Addr, cfgParcel.prefixLength, e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set the IPv4 address of the interface.
+ */
public boolean setIPv4Address(LinkAddress address) {
- final InterfaceConfiguration ifcg = new InterfaceConfiguration();
- ifcg.setLinkAddress(address);
- try {
- mNMS.setInterfaceConfig(mIfName, ifcg);
- if (DBG) mLog.log("IPv4 configuration succeeded");
- } catch (IllegalStateException | RemoteException e) {
- logError("IPv4 configuration failed: %s", e);
+ if (!(address.getAddress() instanceof Inet4Address)) {
return false;
}
- return true;
+ final InterfaceConfiguration ifConfig = new InterfaceConfiguration();
+ ifConfig.setLinkAddress(address);
+ return setInterfaceConfig(ifConfig);
}
+ /**
+ * Clear the IPv4Address of the interface.
+ */
public boolean clearIPv4Address() {
+ final InterfaceConfiguration ifConfig = new InterfaceConfiguration();
+ ifConfig.setLinkAddress(new LinkAddress("0.0.0.0/0"));
+ return setInterfaceConfig(ifConfig);
+ }
+
+ private boolean setEnableIPv6(boolean enabled) {
try {
- final InterfaceConfiguration ifcg = new InterfaceConfiguration();
- ifcg.setLinkAddress(new LinkAddress("0.0.0.0/0"));
- mNMS.setInterfaceConfig(mIfName, ifcg);
- } catch (IllegalStateException | RemoteException e) {
- logError("Failed to clear IPv4 address on interface %s: %s", mIfName, e);
+ mNetd.interfaceSetEnableIPv6(mIfName, enabled);
+ } catch (RemoteException | ServiceSpecificException e) {
+ logError("%s IPv6 failed: %s", (enabled ? "enabling" : "disabling"), e);
return false;
}
return true;
}
+ /**
+ * Enable IPv6 on the interface.
+ */
public boolean enableIPv6() {
- try {
- mNMS.enableIpv6(mIfName);
- } catch (IllegalStateException | RemoteException e) {
- logError("enabling IPv6 failed: %s", e);
- return false;
- }
- return true;
+ return setEnableIPv6(true);
}
+ /**
+ * Disable IPv6 on the interface.
+ */
public boolean disableIPv6() {
- try {
- mNMS.disableIpv6(mIfName);
- } catch (IllegalStateException | RemoteException e) {
- logError("disabling IPv6 failed: %s", e);
- return false;
- }
- return true;
+ return setEnableIPv6(false);
}
+ /**
+ * Enable or disable IPv6 privacy extensions on the interface.
+ * @param enabled Whether the extensions should be enabled.
+ */
public boolean setIPv6PrivacyExtensions(boolean enabled) {
try {
- mNMS.setInterfaceIpv6PrivacyExtensions(mIfName, enabled);
- } catch (IllegalStateException | RemoteException e) {
- logError("error setting IPv6 privacy extensions: %s", e);
+ mNetd.interfaceSetIPv6PrivacyExtensions(mIfName, enabled);
+ } catch (RemoteException | ServiceSpecificException e) {
+ logError("error %s IPv6 privacy extensions: %s",
+ (enabled ? "enabling" : "disabling"), e);
return false;
}
return true;
}
+ /**
+ * Set IPv6 address generation mode on the interface.
+ *
+ * <p>IPv6 should be disabled before changing the mode.
+ */
public boolean setIPv6AddrGenModeIfSupported(int mode) {
try {
- mNMS.setIPv6AddrGenMode(mIfName, mode);
+ mNetd.setIPv6AddrGenMode(mIfName, mode);
} catch (RemoteException e) {
logError("Unable to set IPv6 addrgen mode: %s", e);
return false;
@@ -121,10 +142,16 @@
return true;
}
+ /**
+ * Add an address to the interface.
+ */
public boolean addAddress(LinkAddress addr) {
return addAddress(addr.getAddress(), addr.getPrefixLength());
}
+ /**
+ * Add an address to the interface.
+ */
public boolean addAddress(InetAddress ip, int prefixLen) {
try {
mNetd.interfaceAddAddress(mIfName, ip.getHostAddress(), prefixLen);
@@ -135,6 +162,9 @@
return true;
}
+ /**
+ * Remove an address from the interface.
+ */
public boolean removeAddress(InetAddress ip, int prefixLen) {
try {
mNetd.interfaceDelAddress(mIfName, ip.getHostAddress(), prefixLen);
@@ -145,9 +175,12 @@
return true;
}
+ /**
+ * Remove all addresses from the interface.
+ */
public boolean clearAllAddresses() {
try {
- mNMS.clearInterfaceAddresses(mIfName);
+ mNetd.interfaceClearAddrs(mIfName);
} catch (Exception e) {
logError("Failed to clear addresses: %s", e);
return false;
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index 9f15573..a61c2ef 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 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.
@@ -16,223 +16,206 @@
package android.net.ip;
-import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable;
import android.content.Context;
-import android.net.DhcpResults;
-import android.net.INetd;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
import android.net.LinkProperties;
-import android.net.LinkProperties.ProvisioningChange;
import android.net.Network;
-import android.net.ProvisioningConfigurationParcelable;
import android.net.ProxyInfo;
-import android.net.ProxyInfoParcelable;
-import android.net.RouteInfo;
import android.net.StaticIpConfiguration;
import android.net.apf.ApfCapabilities;
-import android.net.apf.ApfFilter;
-import android.net.dhcp.DhcpClient;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.IpManagerEvent;
-import android.net.shared.InitialConfiguration;
-import android.net.util.InterfaceParams;
-import android.net.util.MultinetworkPolicyTracker;
-import android.net.util.NetdService;
-import android.net.util.SharedLog;
import android.os.ConditionVariable;
import android.os.INetworkManagementService;
-import android.os.Message;
import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.LocalLog;
import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.IState;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.MessageUtils;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.internal.util.WakeupMessage;
-import com.android.server.net.NetlinkTracker;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.net.InetAddress;
-import java.util.Collection;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
/**
- * IpClient
- *
- * This class provides the interface to IP-layer provisioning and maintenance
- * functionality that can be used by transport layers like Wi-Fi, Ethernet,
- * et cetera.
- *
- * [ Lifetime ]
- * IpClient is designed to be instantiated as soon as the interface name is
- * known and can be as long-lived as the class containing it (i.e. declaring
- * it "private final" is okay).
- *
+ * Proxy for the IpClient in the NetworkStack. To be removed once clients are migrated.
* @hide
*/
-public class IpClient extends StateMachine {
- private static final boolean DBG = false;
+public class IpClient {
+ private static final String TAG = IpClient.class.getSimpleName();
+ private static final int IPCLIENT_BLOCK_TIMEOUT_MS = 10_000;
- // For message logging.
- private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class };
- private static final SparseArray<String> sWhatToString =
- MessageUtils.findMessageNames(sMessageClasses);
- // Two static concurrent hashmaps of interface name to logging classes.
- // One holds StateMachine logs and the other connectivity packet logs.
- private static final ConcurrentHashMap<String, SharedLog> sSmLogs = new ConcurrentHashMap<>();
- private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>();
+ public static final String DUMP_ARG = "ipclient";
- // If |args| is non-empty, assume it's a list of interface names for which
- // we should print IpClient logs (filter out all others).
- public static void dumpAllLogs(PrintWriter writer, String[] args) {
- for (String ifname : sSmLogs.keySet()) {
- if (!ArrayUtils.isEmpty(args) && !ArrayUtils.contains(args, ifname)) continue;
+ private final ConditionVariable mIpClientCv;
+ private final ConditionVariable mShutdownCv;
- writer.println(String.format("--- BEGIN %s ---", ifname));
-
- final SharedLog smLog = sSmLogs.get(ifname);
- if (smLog != null) {
- writer.println("State machine log:");
- smLog.dump(null, writer, null);
- }
-
- writer.println("");
-
- final LocalLog pktLog = sPktLogs.get(ifname);
- if (pktLog != null) {
- writer.println("Connectivity packet log:");
- pktLog.readOnlyLocalLog().dump(null, writer, null);
- }
-
- writer.println(String.format("--- END %s ---", ifname));
- }
- }
+ private volatile IIpClient mIpClient;
/**
- * TODO: remove after migrating clients to use IpClientCallbacks directly
* @see IpClientCallbacks
*/
public static class Callback extends IpClientCallbacks {}
/**
- * TODO: remove once clients are migrated to IpClientUtil.WaitForProvisioningCallback
- * @see IpClientUtil.WaitForProvisioningCallbacks
+ * IpClient callback that allows clients to block until provisioning is complete.
*/
- public static class WaitForProvisioningCallback
- extends IpClientUtil.WaitForProvisioningCallbacks {}
+ public static class WaitForProvisioningCallback extends Callback {
+ private final ConditionVariable mCV = new ConditionVariable();
+ private LinkProperties mCallbackLinkProperties;
- // Use a wrapper class to log in order to ensure complete and detailed
- // logging. This method is lighter weight than annotations/reflection
- // and has the following benefits:
- //
- // - No invoked method can be forgotten.
- // Any new method added to IpClient.Callback must be overridden
- // here or it will never be called.
- //
- // - No invoking call site can be forgotten.
- // Centralized logging in this way means call sites don't need to
- // remember to log, and therefore no call site can be forgotten.
- //
- // - No variation in log format among call sites.
- // Encourages logging of any available arguments, and all call sites
- // are necessarily logged identically.
- //
- // NOTE: Log first because passed objects may or may not be thread-safe and
- // once passed on to the callback they may be modified by another thread.
- //
- // TODO: Find an lighter weight approach.
- private class LoggingCallbackWrapper extends IpClientCallbacks {
- private static final String PREFIX = "INVOKE ";
- private final IpClientCallbacks mCallback;
-
- LoggingCallbackWrapper(IpClientCallbacks callback) {
- mCallback = (callback != null) ? callback : new IpClientCallbacks();
+ /**
+ * Block until either {@link #onProvisioningSuccess(LinkProperties)} or
+ * {@link #onProvisioningFailure(LinkProperties)} is called.
+ */
+ public LinkProperties waitForProvisioning() {
+ mCV.block();
+ return mCallbackLinkProperties;
}
- private void log(String msg) {
- mLog.log(PREFIX + msg);
- }
-
- @Override
- public void onPreDhcpAction() {
- log("onPreDhcpAction()");
- mCallback.onPreDhcpAction();
- }
- @Override
- public void onPostDhcpAction() {
- log("onPostDhcpAction()");
- mCallback.onPostDhcpAction();
- }
- @Override
- public void onNewDhcpResults(DhcpResults dhcpResults) {
- log("onNewDhcpResults({" + dhcpResults + "})");
- mCallback.onNewDhcpResults(dhcpResults);
- }
@Override
public void onProvisioningSuccess(LinkProperties newLp) {
- log("onProvisioningSuccess({" + newLp + "})");
- mCallback.onProvisioningSuccess(newLp);
+ mCallbackLinkProperties = newLp;
+ mCV.open();
}
+
@Override
public void onProvisioningFailure(LinkProperties newLp) {
- log("onProvisioningFailure({" + newLp + "})");
- mCallback.onProvisioningFailure(newLp);
+ mCallbackLinkProperties = null;
+ mCV.open();
}
+ }
+
+ private class CallbackImpl extends IpClientUtil.IpClientCallbacksProxy {
+ /**
+ * Create a new IpClientCallbacksProxy.
+ */
+ CallbackImpl(IpClientCallbacks cb) {
+ super(cb);
+ }
+
@Override
- public void onLinkPropertiesChange(LinkProperties newLp) {
- log("onLinkPropertiesChange({" + newLp + "})");
- mCallback.onLinkPropertiesChange(newLp);
+ public void onIpClientCreated(IIpClient ipClient) {
+ mIpClient = ipClient;
+ mIpClientCv.open();
+ super.onIpClientCreated(ipClient);
}
- @Override
- public void onReachabilityLost(String logMsg) {
- log("onReachabilityLost(" + logMsg + ")");
- mCallback.onReachabilityLost(logMsg);
- }
+
@Override
public void onQuit() {
- log("onQuit()");
- mCallback.onQuit();
+ mShutdownCv.open();
+ super.onQuit();
}
- @Override
- public void installPacketFilter(byte[] filter) {
- log("installPacketFilter(byte[" + filter.length + "])");
- mCallback.installPacketFilter(filter);
+ }
+
+ /**
+ * Create a new IpClient.
+ */
+ public IpClient(Context context, String iface, Callback callback) {
+ mIpClientCv = new ConditionVariable(false);
+ mShutdownCv = new ConditionVariable(false);
+
+ IpClientUtil.makeIpClient(context, iface, new CallbackImpl(callback));
+ }
+
+ /**
+ * @see IpClient#IpClient(Context, String, IpClient.Callback)
+ */
+ public IpClient(Context context, String iface, Callback callback,
+ INetworkManagementService nms) {
+ this(context, iface, callback);
+ }
+
+ private interface IpClientAction {
+ void useIpClient(IIpClient ipClient) throws RemoteException;
+ }
+
+ private void doWithIpClient(IpClientAction action) {
+ mIpClientCv.block(IPCLIENT_BLOCK_TIMEOUT_MS);
+ try {
+ action.useIpClient(mIpClient);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error communicating with IpClient", e);
}
- @Override
- public void startReadPacketFilter() {
- log("startReadPacketFilter()");
- mCallback.startReadPacketFilter();
- }
- @Override
- public void setFallbackMulticastFilter(boolean enabled) {
- log("setFallbackMulticastFilter(" + enabled + ")");
- mCallback.setFallbackMulticastFilter(enabled);
- }
- @Override
- public void setNeighborDiscoveryOffload(boolean enable) {
- log("setNeighborDiscoveryOffload(" + enable + ")");
- mCallback.setNeighborDiscoveryOffload(enable);
- }
+ }
+
+ /**
+ * Notify IpClient that PreDhcpAction is completed.
+ */
+ public void completedPreDhcpAction() {
+ doWithIpClient(c -> c.completedPreDhcpAction());
+ }
+
+ /**
+ * Confirm the provisioning configuration.
+ */
+ public void confirmConfiguration() {
+ doWithIpClient(c -> c.confirmConfiguration());
+ }
+
+ /**
+ * Notify IpClient that packet filter read is complete.
+ */
+ public void readPacketFilterComplete(byte[] data) {
+ doWithIpClient(c -> c.readPacketFilterComplete(data));
+ }
+
+ /**
+ * Shutdown the IpClient altogether.
+ */
+ public void shutdown() {
+ doWithIpClient(c -> c.shutdown());
+ }
+
+ /**
+ * Start the IpClient provisioning.
+ */
+ public void startProvisioning(ProvisioningConfiguration config) {
+ doWithIpClient(c -> c.startProvisioning(config.toStableParcelable()));
+ }
+
+ /**
+ * Stop the IpClient.
+ */
+ public void stop() {
+ doWithIpClient(c -> c.stop());
+ }
+
+ /**
+ * Set the IpClient TCP buffer sizes.
+ */
+ public void setTcpBufferSizes(String tcpBufferSizes) {
+ doWithIpClient(c -> c.setTcpBufferSizes(tcpBufferSizes));
+ }
+
+ /**
+ * Set the IpClient HTTP proxy.
+ */
+ public void setHttpProxy(ProxyInfo proxyInfo) {
+ doWithIpClient(c -> c.setHttpProxy(toStableParcelable(proxyInfo)));
+ }
+
+ /**
+ * Set the IpClient multicast filter.
+ */
+ public void setMulticastFilter(boolean enabled) {
+ doWithIpClient(c -> c.setMulticastFilter(enabled));
+ }
+
+ /**
+ * Dump IpClient logs.
+ */
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ doWithIpClient(c -> IpClientUtil.dumpIpClient(c, fd, pw, args));
+ }
+
+ /**
+ * Block until IpClient shutdown.
+ */
+ public void awaitShutdown() {
+ mShutdownCv.block(IPCLIENT_BLOCK_TIMEOUT_MS);
+ }
+
+ /**
+ * Create a new ProvisioningConfiguration.
+ */
+ public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
+ return new ProvisioningConfiguration.Builder();
}
/**
@@ -334,1432 +317,4 @@
}
}
}
-
- public static final String DUMP_ARG = "ipclient";
- public static final String DUMP_ARG_CONFIRM = "confirm";
-
- private static final int CMD_TERMINATE_AFTER_STOP = 1;
- private static final int CMD_STOP = 2;
- private static final int CMD_START = 3;
- private static final int CMD_CONFIRM = 4;
- private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5;
- // Triggered by NetlinkTracker to communicate netlink events.
- private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
- private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7;
- private static final int CMD_UPDATE_HTTP_PROXY = 8;
- private static final int CMD_SET_MULTICAST_FILTER = 9;
- private static final int EVENT_PROVISIONING_TIMEOUT = 10;
- private static final int EVENT_DHCPACTION_TIMEOUT = 11;
- private static final int EVENT_READ_PACKET_FILTER_COMPLETE = 12;
-
- // Internal commands to use instead of trying to call transitionTo() inside
- // a given State's enter() method. Calling transitionTo() from enter/exit
- // encounters a Log.wtf() that can cause trouble on eng builds.
- private static final int CMD_JUMP_STARTED_TO_RUNNING = 100;
- private static final int CMD_JUMP_RUNNING_TO_STOPPING = 101;
- private static final int CMD_JUMP_STOPPING_TO_STOPPED = 102;
-
- private static final int MAX_LOG_RECORDS = 500;
- private static final int MAX_PACKET_RECORDS = 100;
-
- private static final boolean NO_CALLBACKS = false;
- private static final boolean SEND_CALLBACKS = true;
-
- // This must match the interface prefix in clatd.c.
- // TODO: Revert this hack once IpClient and Nat464Xlat work in concert.
- private static final String CLAT_PREFIX = "v4-";
-
- private static final int IMMEDIATE_FAILURE_DURATION = 0;
-
- private final State mStoppedState = new StoppedState();
- private final State mStoppingState = new StoppingState();
- private final State mStartedState = new StartedState();
- private final State mRunningState = new RunningState();
-
- private final String mTag;
- private final Context mContext;
- private final String mInterfaceName;
- private final String mClatInterfaceName;
- @VisibleForTesting
- protected final IpClientCallbacks mCallback;
- private final Dependencies mDependencies;
- private final CountDownLatch mShutdownLatch;
- private final INetworkManagementService mNwService;
- private final NetlinkTracker mNetlinkTracker;
- private final WakeupMessage mProvisioningTimeoutAlarm;
- private final WakeupMessage mDhcpActionTimeoutAlarm;
- private final SharedLog mLog;
- private final LocalLog mConnectivityPacketLog;
- private final MessageHandlingLogger mMsgStateLogger;
- private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
- private final InterfaceController mInterfaceCtrl;
-
- private InterfaceParams mInterfaceParams;
-
- /**
- * Non-final member variables accessed only from within our StateMachine.
- */
- private LinkProperties mLinkProperties;
- private android.net.shared.ProvisioningConfiguration mConfiguration;
- private MultinetworkPolicyTracker mMultinetworkPolicyTracker;
- private IpReachabilityMonitor mIpReachabilityMonitor;
- private DhcpClient mDhcpClient;
- private DhcpResults mDhcpResults;
- private String mTcpBufferSizes;
- private ProxyInfo mHttpProxy;
- private ApfFilter mApfFilter;
- private boolean mMulticastFiltering;
- private long mStartTimeMillis;
-
- /**
- * Reading the snapshot is an asynchronous operation initiated by invoking
- * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an
- * EVENT_READ_PACKET_FILTER_COMPLETE message. The mApfDataSnapshotComplete condition variable
- * signals when a new snapshot is ready.
- */
- private final ConditionVariable mApfDataSnapshotComplete = new ConditionVariable();
-
- public static class Dependencies {
- public INetworkManagementService getNMS() {
- return INetworkManagementService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
- }
-
- public INetd getNetd() {
- return NetdService.getInstance();
- }
-
- /**
- * Get interface parameters for the specified interface.
- */
- public InterfaceParams getInterfaceParams(String ifname) {
- return InterfaceParams.getByName(ifname);
- }
- }
-
- public IpClient(Context context, String ifName, IpClientCallbacks callback) {
- this(context, ifName, callback, new Dependencies());
- }
-
- /**
- * An expanded constructor, useful for dependency injection.
- * TODO: migrate all test users to mock IpClient directly and remove this ctor.
- */
- public IpClient(Context context, String ifName, IpClientCallbacks callback,
- INetworkManagementService nwService) {
- this(context, ifName, callback, new Dependencies() {
- @Override
- public INetworkManagementService getNMS() {
- return nwService;
- }
- });
- }
-
- @VisibleForTesting
- IpClient(Context context, String ifName, IpClientCallbacks callback, Dependencies deps) {
- super(IpClient.class.getSimpleName() + "." + ifName);
- Preconditions.checkNotNull(ifName);
- Preconditions.checkNotNull(callback);
-
- mTag = getName();
-
- mContext = context;
- mInterfaceName = ifName;
- mClatInterfaceName = CLAT_PREFIX + ifName;
- mCallback = new LoggingCallbackWrapper(callback);
- mDependencies = deps;
- mShutdownLatch = new CountDownLatch(1);
- mNwService = deps.getNMS();
-
- sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag));
- mLog = sSmLogs.get(mInterfaceName);
- sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS));
- mConnectivityPacketLog = sPktLogs.get(mInterfaceName);
- mMsgStateLogger = new MessageHandlingLogger();
-
- // TODO: Consider creating, constructing, and passing in some kind of
- // InterfaceController.Dependencies class.
- mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, deps.getNetd(), mLog);
-
- mNetlinkTracker = new NetlinkTracker(
- mInterfaceName,
- new NetlinkTracker.Callback() {
- @Override
- public void update() {
- sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
- }
- }) {
- @Override
- public void interfaceAdded(String iface) {
- super.interfaceAdded(iface);
- if (mClatInterfaceName.equals(iface)) {
- mCallback.setNeighborDiscoveryOffload(false);
- } else if (!mInterfaceName.equals(iface)) {
- return;
- }
-
- final String msg = "interfaceAdded(" + iface + ")";
- logMsg(msg);
- }
-
- @Override
- public void interfaceRemoved(String iface) {
- super.interfaceRemoved(iface);
- // TODO: Also observe mInterfaceName going down and take some
- // kind of appropriate action.
- if (mClatInterfaceName.equals(iface)) {
- // TODO: consider sending a message to the IpClient main
- // StateMachine thread, in case "NDO enabled" state becomes
- // tied to more things that 464xlat operation.
- mCallback.setNeighborDiscoveryOffload(true);
- } else if (!mInterfaceName.equals(iface)) {
- return;
- }
-
- final String msg = "interfaceRemoved(" + iface + ")";
- logMsg(msg);
- }
-
- private void logMsg(String msg) {
- Log.d(mTag, msg);
- getHandler().post(() -> mLog.log("OBSERVED " + msg));
- }
- };
-
- mLinkProperties = new LinkProperties();
- mLinkProperties.setInterfaceName(mInterfaceName);
-
- mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
- mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
- mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
- mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
-
- // Anything the StateMachine may access must have been instantiated
- // before this point.
- configureAndStartStateMachine();
-
- // Anything that may send messages to the StateMachine must only be
- // configured to do so after the StateMachine has started (above).
- startStateMachineUpdaters();
- }
-
- /**
- * Make a IIpClient connector to communicate with this IpClient.
- */
- public IIpClient makeConnector() {
- return new IpClientConnector();
- }
-
- class IpClientConnector extends IIpClient.Stub {
- @Override
- public void completedPreDhcpAction() {
- IpClient.this.completedPreDhcpAction();
- }
- @Override
- public void confirmConfiguration() {
- IpClient.this.confirmConfiguration();
- }
- @Override
- public void readPacketFilterComplete(byte[] data) {
- IpClient.this.readPacketFilterComplete(data);
- }
- @Override
- public void shutdown() {
- IpClient.this.shutdown();
- }
- @Override
- public void startProvisioning(ProvisioningConfigurationParcelable req) {
- IpClient.this.startProvisioning(
- android.net.shared.ProvisioningConfiguration.fromStableParcelable(req));
- }
- @Override
- public void stop() {
- IpClient.this.stop();
- }
- @Override
- public void setTcpBufferSizes(String tcpBufferSizes) {
- IpClient.this.setTcpBufferSizes(tcpBufferSizes);
- }
- @Override
- public void setHttpProxy(ProxyInfoParcelable proxyInfo) {
- IpClient.this.setHttpProxy(fromStableParcelable(proxyInfo));
- }
- @Override
- public void setMulticastFilter(boolean enabled) {
- IpClient.this.setMulticastFilter(enabled);
- }
- // TODO: remove and have IpClient logs dumped in NetworkStack dumpsys
- public void dumpIpClientLogs(FileDescriptor fd, PrintWriter pw, String[] args) {
- IpClient.this.dump(fd, pw, args);
- }
- }
-
- private void configureAndStartStateMachine() {
- // CHECKSTYLE:OFF IndentationCheck
- addState(mStoppedState);
- addState(mStartedState);
- addState(mRunningState, mStartedState);
- addState(mStoppingState);
- // CHECKSTYLE:ON IndentationCheck
-
- setInitialState(mStoppedState);
-
- super.start();
- }
-
- private void startStateMachineUpdaters() {
- try {
- mNwService.registerObserver(mNetlinkTracker);
- } catch (RemoteException e) {
- logError("Couldn't register NetlinkTracker: %s", e);
- }
- }
-
- private void stopStateMachineUpdaters() {
- try {
- mNwService.unregisterObserver(mNetlinkTracker);
- } catch (RemoteException e) {
- logError("Couldn't unregister NetlinkTracker: %s", e);
- }
- }
-
- @Override
- protected void onQuitting() {
- mCallback.onQuit();
- mShutdownLatch.countDown();
- }
-
- /**
- * Shut down this IpClient instance altogether.
- */
- public void shutdown() {
- stop();
- sendMessage(CMD_TERMINATE_AFTER_STOP);
- }
-
- // In order to avoid deadlock, this method MUST NOT be called on the
- // IpClient instance's thread. This prohibition includes code executed by
- // when methods on the passed-in IpClient.Callback instance are called.
- public void awaitShutdown() {
- try {
- mShutdownLatch.await();
- } catch (InterruptedException e) {
- mLog.e("Interrupted while awaiting shutdown: " + e);
- }
- }
-
- public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
- return new ProvisioningConfiguration.Builder();
- }
-
- /**
- * Start provisioning with the provided parameters.
- */
- public void startProvisioning(android.net.shared.ProvisioningConfiguration req) {
- if (!req.isValid()) {
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
- return;
- }
-
- mInterfaceParams = mDependencies.getInterfaceParams(mInterfaceName);
- if (mInterfaceParams == null) {
- logError("Failed to find InterfaceParams for " + mInterfaceName);
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND);
- return;
- }
-
- mCallback.setNeighborDiscoveryOffload(true);
- sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req));
- }
-
- // TODO: Delete this.
- public void startProvisioning(StaticIpConfiguration staticIpConfig) {
- startProvisioning(buildProvisioningConfiguration()
- .withStaticConfiguration(staticIpConfig)
- .build());
- }
-
- public void startProvisioning() {
- startProvisioning(new android.net.shared.ProvisioningConfiguration());
- }
-
- /**
- * Stop this IpClient.
- *
- * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}.
- */
- public void stop() {
- sendMessage(CMD_STOP);
- }
-
- /**
- * Confirm the provisioning configuration.
- */
- public void confirmConfiguration() {
- sendMessage(CMD_CONFIRM);
- }
-
- /**
- * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be
- * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to
- * proceed.
- */
- public void completedPreDhcpAction() {
- sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
- }
-
- /**
- * Indicate that packet filter read is complete.
- */
- public void readPacketFilterComplete(byte[] data) {
- sendMessage(EVENT_READ_PACKET_FILTER_COMPLETE, data);
- }
-
- /**
- * Set the TCP buffer sizes to use.
- *
- * This may be called, repeatedly, at any time before or after a call to
- * #startProvisioning(). The setting is cleared upon calling #stop().
- */
- public void setTcpBufferSizes(String tcpBufferSizes) {
- sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
- }
-
- /**
- * Set the HTTP Proxy configuration to use.
- *
- * This may be called, repeatedly, at any time before or after a call to
- * #startProvisioning(). The setting is cleared upon calling #stop().
- */
- public void setHttpProxy(ProxyInfo proxyInfo) {
- sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
- }
-
- /**
- * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering,
- * if not, Callback.setFallbackMulticastFilter() is called.
- */
- public void setMulticastFilter(boolean enabled) {
- sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
- }
-
- /**
- * Dump logs of this IpClient.
- */
- public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
- if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
- // Execute confirmConfiguration() and take no further action.
- confirmConfiguration();
- return;
- }
-
- // Thread-unsafe access to mApfFilter but just used for debugging.
- final ApfFilter apfFilter = mApfFilter;
- final android.net.shared.ProvisioningConfiguration provisioningConfig = mConfiguration;
- final ApfCapabilities apfCapabilities = (provisioningConfig != null)
- ? provisioningConfig.mApfCapabilities : null;
-
- IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- pw.println(mTag + " APF dump:");
- pw.increaseIndent();
- if (apfFilter != null) {
- if (apfCapabilities.hasDataAccess()) {
- // Request a new snapshot, then wait for it.
- mApfDataSnapshotComplete.close();
- mCallback.startReadPacketFilter();
- if (!mApfDataSnapshotComplete.block(1000)) {
- pw.print("TIMEOUT: DUMPING STALE APF SNAPSHOT");
- }
- }
- apfFilter.dump(pw);
-
- } else {
- pw.print("No active ApfFilter; ");
- if (provisioningConfig == null) {
- pw.println("IpClient not yet started.");
- } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
- pw.println("Hardware does not support APF.");
- } else {
- pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
- }
- }
- pw.decreaseIndent();
- pw.println();
- pw.println(mTag + " current ProvisioningConfiguration:");
- pw.increaseIndent();
- pw.println(Objects.toString(provisioningConfig, "N/A"));
- pw.decreaseIndent();
-
- final IpReachabilityMonitor iprm = mIpReachabilityMonitor;
- if (iprm != null) {
- pw.println();
- pw.println(mTag + " current IpReachabilityMonitor state:");
- pw.increaseIndent();
- iprm.dump(pw);
- pw.decreaseIndent();
- }
-
- pw.println();
- pw.println(mTag + " StateMachine dump:");
- pw.increaseIndent();
- mLog.dump(fd, pw, args);
- pw.decreaseIndent();
-
- pw.println();
- pw.println(mTag + " connectivity packet log:");
- pw.println();
- pw.println("Debug with python and scapy via:");
- pw.println("shell$ python");
- pw.println(">>> from scapy import all as scapy");
- pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
- pw.println();
-
- pw.increaseIndent();
- mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
- pw.decreaseIndent();
- }
-
-
- /**
- * Internals.
- */
-
- @Override
- protected String getWhatToString(int what) {
- return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
- }
-
- @Override
- protected String getLogRecString(Message msg) {
- final String logLine = String.format(
- "%s/%d %d %d %s [%s]",
- mInterfaceName, (mInterfaceParams == null) ? -1 : mInterfaceParams.index,
- msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
-
- final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
- mLog.log(richerLogLine);
- if (DBG) {
- Log.d(mTag, richerLogLine);
- }
-
- mMsgStateLogger.reset();
- return logLine;
- }
-
- @Override
- protected boolean recordLogRec(Message msg) {
- // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
- // and we already log any LinkProperties change that results in an
- // invocation of IpClient.Callback#onLinkPropertiesChange().
- final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
- if (!shouldLog) {
- mMsgStateLogger.reset();
- }
- return shouldLog;
- }
-
- private void logError(String fmt, Object... args) {
- final String msg = "ERROR " + String.format(fmt, args);
- Log.e(mTag, msg);
- mLog.log(msg);
- }
-
- // This needs to be called with care to ensure that our LinkProperties
- // are in sync with the actual LinkProperties of the interface. For example,
- // we should only call this if we know for sure that there are no IP addresses
- // assigned to the interface, etc.
- private void resetLinkProperties() {
- mNetlinkTracker.clearLinkProperties();
- mConfiguration = null;
- mDhcpResults = null;
- mTcpBufferSizes = "";
- mHttpProxy = null;
-
- mLinkProperties = new LinkProperties();
- mLinkProperties.setInterfaceName(mInterfaceName);
- }
-
- private void recordMetric(final int type) {
- // We may record error metrics prior to starting.
- // Map this to IMMEDIATE_FAILURE_DURATION.
- final long duration = (mStartTimeMillis > 0)
- ? (SystemClock.elapsedRealtime() - mStartTimeMillis)
- : IMMEDIATE_FAILURE_DURATION;
- mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
- }
-
- // For now: use WifiStateMachine's historical notion of provisioned.
- @VisibleForTesting
- static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
- // For historical reasons, we should connect even if all we have is
- // an IPv4 address and nothing else.
- if (lp.hasIPv4Address() || lp.isProvisioned()) {
- return true;
- }
- if (config == null) {
- return false;
- }
-
- // When an InitialConfiguration is specified, ignore any difference with previous
- // properties and instead check if properties observed match the desired properties.
- return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
- }
-
- // TODO: Investigate folding all this into the existing static function
- // LinkProperties.compareProvisioning() or some other single function that
- // takes two LinkProperties objects and returns a ProvisioningChange
- // object that is a correct and complete assessment of what changed, taking
- // account of the asymmetries described in the comments in this function.
- // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
- private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
- ProvisioningChange delta;
- InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
- final boolean wasProvisioned = isProvisioned(oldLp, config);
- final boolean isProvisioned = isProvisioned(newLp, config);
-
- if (!wasProvisioned && isProvisioned) {
- delta = ProvisioningChange.GAINED_PROVISIONING;
- } else if (wasProvisioned && isProvisioned) {
- delta = ProvisioningChange.STILL_PROVISIONED;
- } else if (!wasProvisioned && !isProvisioned) {
- delta = ProvisioningChange.STILL_NOT_PROVISIONED;
- } else {
- // (wasProvisioned && !isProvisioned)
- //
- // Note that this is true even if we lose a configuration element
- // (e.g., a default gateway) that would not be required to advance
- // into provisioned state. This is intended: if we have a default
- // router and we lose it, that's a sure sign of a problem, but if
- // we connect to a network with no IPv4 DNS servers, we consider
- // that to be a network without DNS servers and connect anyway.
- //
- // See the comment below.
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
- final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
- final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
-
- // If bad wifi avoidance is disabled, then ignore IPv6 loss of
- // provisioning. Otherwise, when a hotspot that loses Internet
- // access sends out a 0-lifetime RA to its clients, the clients
- // will disconnect and then reconnect, avoiding the bad hotspot,
- // instead of getting stuck on the bad hotspot. http://b/31827713 .
- //
- // This is incorrect because if the hotspot then regains Internet
- // access with a different prefix, TCP connections on the
- // deprecated addresses will remain stuck.
- //
- // Note that we can still be disconnected by IpReachabilityMonitor
- // if the IPv6 default gateway (but not the IPv6 DNS servers; see
- // accompanying code in IpReachabilityMonitor) is unreachable.
- final boolean ignoreIPv6ProvisioningLoss = (mMultinetworkPolicyTracker != null)
- && !mMultinetworkPolicyTracker.getAvoidBadWifi();
-
- // Additionally:
- //
- // Partial configurations (e.g., only an IPv4 address with no DNS
- // servers and no default route) are accepted as long as DHCPv4
- // succeeds. On such a network, isProvisioned() will always return
- // false, because the configuration is not complete, but we want to
- // connect anyway. It might be a disconnected network such as a
- // Chromecast or a wireless printer, for example.
- //
- // Because on such a network isProvisioned() will always return false,
- // delta will never be LOST_PROVISIONING. So check for loss of
- // provisioning here too.
- if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- // Additionally:
- //
- // If the previous link properties had a global IPv6 address and an
- // IPv6 default route then also consider the loss of that default route
- // to be a loss of provisioning. See b/27962810.
- if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- return delta;
- }
-
- private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
- switch (delta) {
- case GAINED_PROVISIONING:
- if (DBG) {
- Log.d(mTag, "onProvisioningSuccess()");
- }
- recordMetric(IpManagerEvent.PROVISIONING_OK);
- mCallback.onProvisioningSuccess(newLp);
- break;
-
- case LOST_PROVISIONING:
- if (DBG) {
- Log.d(mTag, "onProvisioningFailure()");
- }
- recordMetric(IpManagerEvent.PROVISIONING_FAIL);
- mCallback.onProvisioningFailure(newLp);
- break;
-
- default:
- if (DBG) {
- Log.d(mTag, "onLinkPropertiesChange()");
- }
- mCallback.onLinkPropertiesChange(newLp);
- break;
- }
- }
-
- // Updates all IpClient-related state concerned with LinkProperties.
- // Returns a ProvisioningChange for possibly notifying other interested
- // parties that are not fronted by IpClient.
- private ProvisioningChange setLinkProperties(LinkProperties newLp) {
- if (mApfFilter != null) {
- mApfFilter.setLinkProperties(newLp);
- }
- if (mIpReachabilityMonitor != null) {
- mIpReachabilityMonitor.updateLinkProperties(newLp);
- }
-
- ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
- mLinkProperties = new LinkProperties(newLp);
-
- if (delta == ProvisioningChange.GAINED_PROVISIONING) {
- // TODO: Add a proper ProvisionedState and cancel the alarm in
- // its enter() method.
- mProvisioningTimeoutAlarm.cancel();
- }
-
- return delta;
- }
-
- private LinkProperties assembleLinkProperties() {
- // [1] Create a new LinkProperties object to populate.
- LinkProperties newLp = new LinkProperties();
- newLp.setInterfaceName(mInterfaceName);
-
- // [2] Pull in data from netlink:
- // - IPv4 addresses
- // - IPv6 addresses
- // - IPv6 routes
- // - IPv6 DNS servers
- //
- // N.B.: this is fundamentally race-prone and should be fixed by
- // changing NetlinkTracker from a hybrid edge/level model to an
- // edge-only model, or by giving IpClient its own netlink socket(s)
- // so as to track all required information directly.
- LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
- newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
- for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
- newLp.addRoute(route);
- }
- addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
-
- // [3] Add in data from DHCPv4, if available.
- //
- // mDhcpResults is never shared with any other owner so we don't have
- // to worry about concurrent modification.
- if (mDhcpResults != null) {
- for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
- newLp.addRoute(route);
- }
- addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
- newLp.setDomains(mDhcpResults.domains);
-
- if (mDhcpResults.mtu != 0) {
- newLp.setMtu(mDhcpResults.mtu);
- }
- }
-
- // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
- if (!TextUtils.isEmpty(mTcpBufferSizes)) {
- newLp.setTcpBufferSizes(mTcpBufferSizes);
- }
- if (mHttpProxy != null) {
- newLp.setHttpProxy(mHttpProxy);
- }
-
- // [5] Add data from InitialConfiguration
- if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
- InitialConfiguration config = mConfiguration.mInitialConfig;
- // Add InitialConfiguration routes and dns server addresses once all addresses
- // specified in the InitialConfiguration have been observed with Netlink.
- if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
- for (IpPrefix prefix : config.directlyConnectedRoutes) {
- newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
- }
- }
- addAllReachableDnsServers(newLp, config.dnsServers);
- }
- final LinkProperties oldLp = mLinkProperties;
- if (DBG) {
- Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
- netlinkLinkProperties, newLp, oldLp));
- }
-
- // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
- // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
- return newLp;
- }
-
- private static void addAllReachableDnsServers(
- LinkProperties lp, Iterable<InetAddress> dnses) {
- // TODO: Investigate deleting this reachability check. We should be
- // able to pass everything down to netd and let netd do evaluation
- // and RFC6724-style sorting.
- for (InetAddress dns : dnses) {
- if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
- lp.addDnsServer(dns);
- }
- }
- }
-
- // Returns false if we have lost provisioning, true otherwise.
- private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
- final LinkProperties newLp = assembleLinkProperties();
- if (Objects.equals(newLp, mLinkProperties)) {
- return true;
- }
- final ProvisioningChange delta = setLinkProperties(newLp);
- if (sendCallbacks) {
- dispatchCallback(delta, newLp);
- }
- return (delta != ProvisioningChange.LOST_PROVISIONING);
- }
-
- private void handleIPv4Success(DhcpResults dhcpResults) {
- mDhcpResults = new DhcpResults(dhcpResults);
- final LinkProperties newLp = assembleLinkProperties();
- final ProvisioningChange delta = setLinkProperties(newLp);
-
- if (DBG) {
- Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
- }
- mCallback.onNewDhcpResults(dhcpResults);
- dispatchCallback(delta, newLp);
- }
-
- private void handleIPv4Failure() {
- // TODO: Investigate deleting this clearIPv4Address() call.
- //
- // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
- // that could trigger a call to this function. If we missed handling
- // that message in StartedState for some reason we would still clear
- // any addresses upon entry to StoppedState.
- mInterfaceCtrl.clearIPv4Address();
- mDhcpResults = null;
- if (DBG) {
- Log.d(mTag, "onNewDhcpResults(null)");
- }
- mCallback.onNewDhcpResults(null);
-
- handleProvisioningFailure();
- }
-
- private void handleProvisioningFailure() {
- final LinkProperties newLp = assembleLinkProperties();
- ProvisioningChange delta = setLinkProperties(newLp);
- // If we've gotten here and we're still not provisioned treat that as
- // a total loss of provisioning.
- //
- // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
- // there was no usable IPv6 obtained before a non-zero provisioning
- // timeout expired.
- //
- // Regardless: GAME OVER.
- if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- dispatchCallback(delta, newLp);
- if (delta == ProvisioningChange.LOST_PROVISIONING) {
- transitionTo(mStoppingState);
- }
- }
-
- private void doImmediateProvisioningFailure(int failureType) {
- logError("onProvisioningFailure(): %s", failureType);
- recordMetric(failureType);
- mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
- }
-
- private boolean startIPv4() {
- // If we have a StaticIpConfiguration attempt to apply it and
- // handle the result accordingly.
- if (mConfiguration.mStaticIpConfig != null) {
- if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
- handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
- } else {
- return false;
- }
- } else {
- // Start DHCPv4.
- mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceParams);
- mDhcpClient.registerForPreDhcpNotification();
- mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
- }
-
- return true;
- }
-
- private boolean startIPv6() {
- return mInterfaceCtrl.setIPv6PrivacyExtensions(true)
- && mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode)
- && mInterfaceCtrl.enableIPv6();
- }
-
- private boolean applyInitialConfig(InitialConfiguration config) {
- // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
- for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
- if (!mInterfaceCtrl.addAddress(addr)) return false;
- }
-
- return true;
- }
-
- private boolean startIpReachabilityMonitor() {
- try {
- // TODO: Fetch these parameters from settings, and install a
- // settings observer to watch for update and re-program these
- // parameters (Q: is this level of dynamic updatability really
- // necessary or does reading from settings at startup suffice?).
- final int numSolicits = 5;
- final int interSolicitIntervalMs = 750;
- setNeighborParameters(mDependencies.getNetd(), mInterfaceName,
- numSolicits, interSolicitIntervalMs);
- } catch (Exception e) {
- mLog.e("Failed to adjust neighbor parameters", e);
- // Carry on using the system defaults (currently: 3, 1000);
- }
-
- try {
- mIpReachabilityMonitor = new IpReachabilityMonitor(
- mContext,
- mInterfaceParams,
- getHandler(),
- mLog,
- new IpReachabilityMonitor.Callback() {
- @Override
- public void notifyLost(InetAddress ip, String logMsg) {
- mCallback.onReachabilityLost(logMsg);
- }
- },
- mMultinetworkPolicyTracker);
- } catch (IllegalArgumentException iae) {
- // Failed to start IpReachabilityMonitor. Log it and call
- // onProvisioningFailure() immediately.
- //
- // See http://b/31038971.
- logError("IpReachabilityMonitor failure: %s", iae);
- mIpReachabilityMonitor = null;
- }
-
- return (mIpReachabilityMonitor != null);
- }
-
- private void stopAllIP() {
- // We don't need to worry about routes, just addresses, because:
- // - disableIpv6() will clear autoconf IPv6 routes as well, and
- // - we don't get IPv4 routes from netlink
- // so we neither react to nor need to wait for changes in either.
-
- mInterfaceCtrl.disableIPv6();
- mInterfaceCtrl.clearAllAddresses();
- }
-
- class StoppedState extends State {
- @Override
- public void enter() {
- stopAllIP();
-
- resetLinkProperties();
- if (mStartTimeMillis > 0) {
- // Completed a life-cycle; send a final empty LinkProperties
- // (cleared in resetLinkProperties() above) and record an event.
- mCallback.onLinkPropertiesChange(new LinkProperties(mLinkProperties));
- recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
- mStartTimeMillis = 0;
- }
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_TERMINATE_AFTER_STOP:
- stopStateMachineUpdaters();
- quit();
- break;
-
- case CMD_STOP:
- break;
-
- case CMD_START:
- mConfiguration = (android.net.shared.ProvisioningConfiguration) msg.obj;
- transitionTo(mStartedState);
- break;
-
- case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- break;
-
- case CMD_UPDATE_TCP_BUFFER_SIZES:
- mTcpBufferSizes = (String) msg.obj;
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- break;
-
- case CMD_UPDATE_HTTP_PROXY:
- mHttpProxy = (ProxyInfo) msg.obj;
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- break;
-
- case CMD_SET_MULTICAST_FILTER:
- mMulticastFiltering = (boolean) msg.obj;
- break;
-
- case DhcpClient.CMD_ON_QUIT:
- // Everything is already stopped.
- logError("Unexpected CMD_ON_QUIT (already stopped).");
- break;
-
- default:
- return NOT_HANDLED;
- }
-
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
- }
-
- class StoppingState extends State {
- @Override
- public void enter() {
- if (mDhcpClient == null) {
- // There's no DHCPv4 for which to wait; proceed to stopped.
- deferMessage(obtainMessage(CMD_JUMP_STOPPING_TO_STOPPED));
- }
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_JUMP_STOPPING_TO_STOPPED:
- transitionTo(mStoppedState);
- break;
-
- case CMD_STOP:
- break;
-
- case DhcpClient.CMD_CLEAR_LINKADDRESS:
- mInterfaceCtrl.clearIPv4Address();
- break;
-
- case DhcpClient.CMD_ON_QUIT:
- mDhcpClient = null;
- transitionTo(mStoppedState);
- break;
-
- default:
- deferMessage(msg);
- }
-
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
- }
-
- class StartedState extends State {
- @Override
- public void enter() {
- mStartTimeMillis = SystemClock.elapsedRealtime();
-
- if (mConfiguration.mProvisioningTimeoutMs > 0) {
- final long alarmTime = SystemClock.elapsedRealtime()
- + mConfiguration.mProvisioningTimeoutMs;
- mProvisioningTimeoutAlarm.schedule(alarmTime);
- }
-
- if (readyToProceed()) {
- deferMessage(obtainMessage(CMD_JUMP_STARTED_TO_RUNNING));
- } else {
- // Clear all IPv4 and IPv6 before proceeding to RunningState.
- // Clean up any leftover state from an abnormal exit from
- // tethering or during an IpClient restart.
- stopAllIP();
- }
- }
-
- @Override
- public void exit() {
- mProvisioningTimeoutAlarm.cancel();
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_JUMP_STARTED_TO_RUNNING:
- transitionTo(mRunningState);
- break;
-
- case CMD_STOP:
- transitionTo(mStoppingState);
- break;
-
- case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- if (readyToProceed()) {
- transitionTo(mRunningState);
- }
- break;
-
- case EVENT_PROVISIONING_TIMEOUT:
- handleProvisioningFailure();
- break;
-
- default:
- // It's safe to process messages out of order because the
- // only message that can both
- // a) be received at this time and
- // b) affect provisioning state
- // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
- deferMessage(msg);
- }
-
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
-
- private boolean readyToProceed() {
- return (!mLinkProperties.hasIPv4Address() && !mLinkProperties.hasGlobalIPv6Address());
- }
- }
-
- class RunningState extends State {
- private ConnectivityPacketTracker mPacketTracker;
- private boolean mDhcpActionInFlight;
-
- @Override
- public void enter() {
- ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration();
- apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;
- apfConfig.multicastFilter = mMulticastFiltering;
- // Get the Configuration for ApfFilter from Context
- apfConfig.ieee802_3Filter =
- mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
- apfConfig.ethTypeBlackList =
- mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList);
- mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback);
- // TODO: investigate the effects of any multicast filtering racing/interfering with the
- // rest of this IP configuration startup.
- if (mApfFilter == null) {
- mCallback.setFallbackMulticastFilter(mMulticastFiltering);
- }
-
- mPacketTracker = createPacketTracker();
- if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
-
- if (mConfiguration.mEnableIPv6 && !startIPv6()) {
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
- enqueueJumpToStoppingState();
- return;
- }
-
- if (mConfiguration.mEnableIPv4 && !startIPv4()) {
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
- enqueueJumpToStoppingState();
- return;
- }
-
- final InitialConfiguration config = mConfiguration.mInitialConfig;
- if ((config != null) && !applyInitialConfig(config)) {
- // TODO introduce a new IpManagerEvent constant to distinguish this error case.
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
- enqueueJumpToStoppingState();
- return;
- }
-
- if (mConfiguration.mUsingMultinetworkPolicyTracker) {
- mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(
- mContext, getHandler(),
- () -> mLog.log("OBSERVED AvoidBadWifi changed"));
- mMultinetworkPolicyTracker.start();
- }
-
- if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
- doImmediateProvisioningFailure(
- IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
- enqueueJumpToStoppingState();
- return;
- }
- }
-
- @Override
- public void exit() {
- stopDhcpAction();
-
- if (mIpReachabilityMonitor != null) {
- mIpReachabilityMonitor.stop();
- mIpReachabilityMonitor = null;
- }
-
- if (mMultinetworkPolicyTracker != null) {
- mMultinetworkPolicyTracker.shutdown();
- mMultinetworkPolicyTracker = null;
- }
-
- if (mDhcpClient != null) {
- mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
- mDhcpClient.doQuit();
- }
-
- if (mPacketTracker != null) {
- mPacketTracker.stop();
- mPacketTracker = null;
- }
-
- if (mApfFilter != null) {
- mApfFilter.shutdown();
- mApfFilter = null;
- }
-
- resetLinkProperties();
- }
-
- private void enqueueJumpToStoppingState() {
- deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING));
- }
-
- private ConnectivityPacketTracker createPacketTracker() {
- try {
- return new ConnectivityPacketTracker(
- getHandler(), mInterfaceParams, mConnectivityPacketLog);
- } catch (IllegalArgumentException e) {
- return null;
- }
- }
-
- private void ensureDhcpAction() {
- if (!mDhcpActionInFlight) {
- mCallback.onPreDhcpAction();
- mDhcpActionInFlight = true;
- final long alarmTime = SystemClock.elapsedRealtime()
- + mConfiguration.mRequestedPreDhcpActionMs;
- mDhcpActionTimeoutAlarm.schedule(alarmTime);
- }
- }
-
- private void stopDhcpAction() {
- mDhcpActionTimeoutAlarm.cancel();
- if (mDhcpActionInFlight) {
- mCallback.onPostDhcpAction();
- mDhcpActionInFlight = false;
- }
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_JUMP_RUNNING_TO_STOPPING:
- case CMD_STOP:
- transitionTo(mStoppingState);
- break;
-
- case CMD_START:
- logError("ALERT: START received in StartedState. Please fix caller.");
- break;
-
- case CMD_CONFIRM:
- // TODO: Possibly introduce a second type of confirmation
- // that both probes (a) on-link neighbors and (b) does
- // a DHCPv4 RENEW. We used to do this on Wi-Fi framework
- // roams.
- if (mIpReachabilityMonitor != null) {
- mIpReachabilityMonitor.probeAll();
- }
- break;
-
- case EVENT_PRE_DHCP_ACTION_COMPLETE:
- // It's possible to reach here if, for example, someone
- // calls completedPreDhcpAction() after provisioning with
- // a static IP configuration.
- if (mDhcpClient != null) {
- mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
- }
- break;
-
- case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
- if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
- transitionTo(mStoppingState);
- }
- break;
-
- case CMD_UPDATE_TCP_BUFFER_SIZES:
- mTcpBufferSizes = (String) msg.obj;
- // This cannot possibly change provisioning state.
- handleLinkPropertiesUpdate(SEND_CALLBACKS);
- break;
-
- case CMD_UPDATE_HTTP_PROXY:
- mHttpProxy = (ProxyInfo) msg.obj;
- // This cannot possibly change provisioning state.
- handleLinkPropertiesUpdate(SEND_CALLBACKS);
- break;
-
- case CMD_SET_MULTICAST_FILTER: {
- mMulticastFiltering = (boolean) msg.obj;
- if (mApfFilter != null) {
- mApfFilter.setMulticastFilter(mMulticastFiltering);
- } else {
- mCallback.setFallbackMulticastFilter(mMulticastFiltering);
- }
- break;
- }
-
- case EVENT_READ_PACKET_FILTER_COMPLETE: {
- if (mApfFilter != null) {
- mApfFilter.setDataSnapshot((byte[]) msg.obj);
- }
- mApfDataSnapshotComplete.open();
- break;
- }
-
- case EVENT_DHCPACTION_TIMEOUT:
- stopDhcpAction();
- break;
-
- case DhcpClient.CMD_PRE_DHCP_ACTION:
- if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
- ensureDhcpAction();
- } else {
- sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
- }
- break;
-
- case DhcpClient.CMD_CLEAR_LINKADDRESS:
- mInterfaceCtrl.clearIPv4Address();
- break;
-
- case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
- final LinkAddress ipAddress = (LinkAddress) msg.obj;
- if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
- mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
- } else {
- logError("Failed to set IPv4 address.");
- dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
- new LinkProperties(mLinkProperties));
- transitionTo(mStoppingState);
- }
- break;
- }
-
- // This message is only received when:
- //
- // a) initial address acquisition succeeds,
- // b) renew succeeds or is NAK'd,
- // c) rebind succeeds or is NAK'd, or
- // c) the lease expires,
- //
- // but never when initial address acquisition fails. The latter
- // condition is now governed by the provisioning timeout.
- case DhcpClient.CMD_POST_DHCP_ACTION:
- stopDhcpAction();
-
- switch (msg.arg1) {
- case DhcpClient.DHCP_SUCCESS:
- handleIPv4Success((DhcpResults) msg.obj);
- break;
- case DhcpClient.DHCP_FAILURE:
- handleIPv4Failure();
- break;
- default:
- logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
- }
- break;
-
- case DhcpClient.CMD_ON_QUIT:
- // DHCPv4 quit early for some reason.
- logError("Unexpected CMD_ON_QUIT.");
- mDhcpClient = null;
- break;
-
- default:
- return NOT_HANDLED;
- }
-
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
- }
-
- private static class MessageHandlingLogger {
- public String processedInState;
- public String receivedInState;
-
- public void reset() {
- processedInState = null;
- receivedInState = null;
- }
-
- public void handled(State processedIn, IState receivedIn) {
- processedInState = processedIn.getClass().getSimpleName();
- receivedInState = receivedIn.getName();
- }
-
- public String toString() {
- return String.format("rcvd_in=%s, proc_in=%s",
- receivedInState, processedInState);
- }
- }
-
- private static void setNeighborParameters(
- INetd netd, String ifName, int numSolicits, int interSolicitIntervalMs)
- throws RemoteException, IllegalArgumentException {
- Preconditions.checkNotNull(netd);
- Preconditions.checkArgument(!TextUtils.isEmpty(ifName));
- Preconditions.checkArgument(numSolicits > 0);
- Preconditions.checkArgument(interSolicitIntervalMs > 0);
-
- for (int family : new Integer[]{INetd.IPV4, INetd.IPV6}) {
- netd.setProcSysNet(family, INetd.NEIGH, ifName, "retrans_time_ms",
- Integer.toString(interSolicitIntervalMs));
- netd.setProcSysNet(family, INetd.NEIGH, ifName, "ucast_solicit",
- Integer.toString(numSolicits));
- }
- }
-
- // TODO: extract out into CollectionUtils.
- static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
- for (T t : coll) {
- if (fn.test(t)) {
- return true;
- }
- }
- return false;
- }
-
- static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
- return !any(coll, not(fn));
- }
-
- static <T> Predicate<T> not(Predicate<T> fn) {
- return (t) -> !fn.test(t);
- }
-
- static <T> String join(String delimiter, Collection<T> coll) {
- return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
- }
-
- static <T> T find(Iterable<T> coll, Predicate<T> fn) {
- for (T t: coll) {
- if (fn.test(t)) {
- return t;
- }
- }
- return null;
- }
-
- static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
- return coll.stream().filter(fn).collect(Collectors.toList());
- }
}
diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java
index 0aec101..2a2a67a 100644
--- a/services/net/java/android/net/ip/IpClientUtil.java
+++ b/services/net/java/android/net/ip/IpClientUtil.java
@@ -16,8 +16,15 @@
package android.net.ip;
+import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable;
+import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+
import android.content.Context;
+import android.net.DhcpResultsParcelable;
import android.net.LinkProperties;
+import android.net.LinkPropertiesParcelable;
+import android.net.NetworkStack;
+import android.net.ip.IIpClientCallbacks;
import android.os.ConditionVariable;
import java.io.FileDescriptor;
@@ -31,8 +38,8 @@
* @hide
*/
public class IpClientUtil {
- // TODO: remove once IpClient dumps are moved to NetworkStack and callers don't need this arg
- public static final String DUMP_ARG = IpClient.DUMP_ARG;
+ // TODO: remove with its callers
+ public static final String DUMP_ARG = "ipclient";
/**
* Subclass of {@link IpClientCallbacks} allowing clients to block until provisioning is
@@ -69,24 +76,129 @@
*
* <p>This is a convenience method to allow clients to use {@link IpClientCallbacks} instead of
* {@link IIpClientCallbacks}.
+ * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)}
*/
public static void makeIpClient(Context context, String ifName, IpClientCallbacks callback) {
- // TODO: request IpClient asynchronously from NetworkStack.
- final IpClient ipClient = new IpClient(context, ifName, callback);
- callback.onIpClientCreated(ipClient.makeConnector());
+ context.getSystemService(NetworkStack.class)
+ .makeIpClient(ifName, new IpClientCallbacksProxy(callback));
+ }
+
+ /**
+ * Create a new IpClient.
+ *
+ * <p>This is a convenience method to allow clients to use {@link IpClientCallbacksProxy}
+ * instead of {@link IIpClientCallbacks}.
+ * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)}
+ */
+ public static void makeIpClient(
+ Context context, String ifName, IpClientCallbacksProxy callback) {
+ context.getSystemService(NetworkStack.class)
+ .makeIpClient(ifName, callback);
+ }
+
+ /**
+ * Wrapper to relay calls from {@link IIpClientCallbacks} to {@link IpClientCallbacks}.
+ */
+ public static class IpClientCallbacksProxy extends IIpClientCallbacks.Stub {
+ protected final IpClientCallbacks mCb;
+
+ /**
+ * Create a new IpClientCallbacksProxy.
+ */
+ public IpClientCallbacksProxy(IpClientCallbacks cb) {
+ mCb = cb;
+ }
+
+ @Override
+ public void onIpClientCreated(IIpClient ipClient) {
+ mCb.onIpClientCreated(ipClient);
+ }
+
+ @Override
+ public void onPreDhcpAction() {
+ mCb.onPreDhcpAction();
+ }
+
+ @Override
+ public void onPostDhcpAction() {
+ mCb.onPostDhcpAction();
+ }
+
+ // This is purely advisory and not an indication of provisioning
+ // success or failure. This is only here for callers that want to
+ // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
+ // DHCPv4 or static IPv4 configuration failure or success can be
+ // determined by whether or not the passed-in DhcpResults object is
+ // null or not.
+ @Override
+ public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
+ mCb.onNewDhcpResults(fromStableParcelable(dhcpResults));
+ }
+
+ @Override
+ public void onProvisioningSuccess(LinkPropertiesParcelable newLp) {
+ mCb.onProvisioningSuccess(fromStableParcelable(newLp));
+ }
+ @Override
+ public void onProvisioningFailure(LinkPropertiesParcelable newLp) {
+ mCb.onProvisioningFailure(fromStableParcelable(newLp));
+ }
+
+ // Invoked on LinkProperties changes.
+ @Override
+ public void onLinkPropertiesChange(LinkPropertiesParcelable newLp) {
+ mCb.onLinkPropertiesChange(fromStableParcelable(newLp));
+ }
+
+ // Called when the internal IpReachabilityMonitor (if enabled) has
+ // detected the loss of a critical number of required neighbors.
+ @Override
+ public void onReachabilityLost(String logMsg) {
+ mCb.onReachabilityLost(logMsg);
+ }
+
+ // Called when the IpClient state machine terminates.
+ @Override
+ public void onQuit() {
+ mCb.onQuit();
+ }
+
+ // Install an APF program to filter incoming packets.
+ @Override
+ public void installPacketFilter(byte[] filter) {
+ mCb.installPacketFilter(filter);
+ }
+
+ // Asynchronously read back the APF program & data buffer from the wifi driver.
+ // Due to Wifi HAL limitations, the current implementation only supports dumping the entire
+ // buffer. In response to this request, the driver returns the data buffer asynchronously
+ // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
+ @Override
+ public void startReadPacketFilter() {
+ mCb.startReadPacketFilter();
+ }
+
+ // If multicast filtering cannot be accomplished with APF, this function will be called to
+ // actuate multicast filtering using another means.
+ @Override
+ public void setFallbackMulticastFilter(boolean enabled) {
+ mCb.setFallbackMulticastFilter(enabled);
+ }
+
+ // Enabled/disable Neighbor Discover offload functionality. This is
+ // called, for example, whenever 464xlat is being started or stopped.
+ @Override
+ public void setNeighborDiscoveryOffload(boolean enable) {
+ mCb.setNeighborDiscoveryOffload(enable);
+ }
}
/**
* Dump logs for the specified IpClient.
- * TODO: remove logging from this method once IpClient logs are dumped in NetworkStack dumpsys,
- * then remove callers and delete.
+ * TODO: remove callers and delete
*/
public static void dumpIpClient(
IIpClient connector, FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!(connector instanceof IpClient.IpClientConnector)) {
- pw.println("Invalid connector");
- return;
- }
- ((IpClient.IpClientConnector) connector).dumpIpClientLogs(fd, pw, args);
+ pw.println("IpClient logs have moved to dumpsys network_stack");
}
}
diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java
index 8b22f68..f7360f5 100644
--- a/services/net/java/android/net/ip/IpServer.java
+++ b/services/net/java/android/net/ip/IpServer.java
@@ -40,7 +40,7 @@
import android.net.ip.RouterAdvertisementDaemon.RaParams;
import android.net.util.InterfaceParams;
import android.net.util.InterfaceSet;
-import android.net.util.NetdService;
+import android.net.shared.NetdService;
import android.net.util.SharedLog;
import android.os.INetworkManagementService;
import android.os.Looper;
@@ -226,7 +226,7 @@
mNetd = deps.getNetdService();
mStatsService = statsService;
mCallback = callback;
- mInterfaceCtrl = new InterfaceController(ifaceName, nMService, mNetd, mLog);
+ mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
mIfaceName = ifaceName;
mInterfaceType = interfaceType;
mLinkProperties = new LinkProperties();
diff --git a/services/net/java/android/net/netlink/NetlinkSocket.java b/services/net/java/android/net/netlink/NetlinkSocket.java
index 40098c1..2a98d90 100644
--- a/services/net/java/android/net/netlink/NetlinkSocket.java
+++ b/services/net/java/android/net/netlink/NetlinkSocket.java
@@ -16,26 +16,26 @@
package android.net.netlink;
+import static android.net.util.SocketUtils.makeNetlinkSocketAddress;
import static android.system.OsConstants.AF_NETLINK;
import static android.system.OsConstants.EIO;
import static android.system.OsConstants.EPROTO;
import static android.system.OsConstants.ETIMEDOUT;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOL_SOCKET;
import static android.system.OsConstants.SO_RCVBUF;
import static android.system.OsConstants.SO_RCVTIMEO;
import static android.system.OsConstants.SO_SNDTIMEO;
-import static android.system.OsConstants.SOCK_DGRAM;
-import static android.system.OsConstants.SOL_SOCKET;
import android.system.ErrnoException;
-import android.system.NetlinkSocketAddress;
import android.system.Os;
import android.system.StructTimeval;
import android.util.Log;
+
import libcore.io.IoUtils;
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
-import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -106,7 +106,7 @@
}
public static void connectToKernel(FileDescriptor fd) throws ErrnoException, SocketException {
- Os.connect(fd, (SocketAddress) (new NetlinkSocketAddress(0, 0)));
+ Os.connect(fd, makeNetlinkSocketAddress(0, 0));
}
private static void checkTimeout(long timeoutMs) {
diff --git a/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java b/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java
index d5213df..51d955d 100644
--- a/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java
+++ b/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java
@@ -182,9 +182,6 @@
parcel.mtu = lp.getMtu();
parcel.tcpBufferSizes = lp.getTcpBufferSizes();
parcel.nat64Prefix = toStableParcelable(lp.getNat64Prefix());
- parcel.stackedLinks = toParcelableArray(
- lp.getStackedLinks(), LinkPropertiesParcelableUtil::toStableParcelable,
- LinkPropertiesParcelable.class);
return parcel;
}
@@ -216,9 +213,6 @@
lp.setMtu(parcel.mtu);
lp.setTcpBufferSizes(parcel.tcpBufferSizes);
lp.setNat64Prefix(fromStableParcelable(parcel.nat64Prefix));
- for (LinkPropertiesParcelable stackedLink : parcel.stackedLinks) {
- lp.addStackedLink(fromStableParcelable(stackedLink));
- }
return lp;
}
}
diff --git a/services/net/java/android/net/util/NetdService.java b/services/net/java/android/net/shared/NetdService.java
similarity index 99%
rename from services/net/java/android/net/util/NetdService.java
rename to services/net/java/android/net/shared/NetdService.java
index 80b2c27..be0f5f2 100644
--- a/services/net/java/android/net/util/NetdService.java
+++ b/services/net/java/android/net/shared/NetdService.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.net.util;
+package android.net.shared;
import android.net.INetd;
import android.os.RemoteException;
diff --git a/services/net/java/android/net/shared/NetworkMonitorUtils.java b/services/net/java/android/net/shared/NetworkMonitorUtils.java
index 463cf2a..3d2a2de 100644
--- a/services/net/java/android/net/shared/NetworkMonitorUtils.java
+++ b/services/net/java/android/net/shared/NetworkMonitorUtils.java
@@ -16,6 +16,11 @@
package android.net.shared;
+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.NET_CAPABILITY_TRUSTED;
+
import android.content.Context;
import android.net.NetworkCapabilities;
import android.provider.Settings;
@@ -58,9 +63,12 @@
* @param dfltNetCap Default requested network capabilities.
* @param nc Network capabilities of the network to test.
*/
- public static boolean isValidationRequired(
- NetworkCapabilities dfltNetCap, NetworkCapabilities nc) {
+ public static boolean isValidationRequired(NetworkCapabilities nc) {
// TODO: Consider requiring validation for DUN networks.
- return dfltNetCap.satisfiedByNetworkCapabilities(nc);
+ return nc != null
+ && nc.hasCapability(NET_CAPABILITY_INTERNET)
+ && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ && nc.hasCapability(NET_CAPABILITY_TRUSTED)
+ && nc.hasCapability(NET_CAPABILITY_NOT_VPN);
}
}
diff --git a/services/net/java/android/net/util/InterfaceParams.java b/services/net/java/android/net/util/InterfaceParams.java
index 7b060da..f6bb873 100644
--- a/services/net/java/android/net/util/InterfaceParams.java
+++ b/services/net/java/android/net/util/InterfaceParams.java
@@ -16,9 +16,6 @@
package android.net.util;
-import static android.net.util.NetworkConstants.ETHER_MTU;
-import static android.net.util.NetworkConstants.IPV6_MIN_MTU;
-
import static com.android.internal.util.Preconditions.checkArgument;
import android.net.MacAddress;
@@ -44,6 +41,11 @@
public final MacAddress macAddr;
public final int defaultMtu;
+ // TODO: move the below to NetworkStackConstants when this class is moved to the NetworkStack.
+ private static final int ETHER_MTU = 1500;
+ private static final int IPV6_MIN_MTU = 1280;
+
+
public static InterfaceParams getByName(String name) {
final NetworkInterface netif = getNetworkInterfaceByName(name);
if (netif == null) return null;
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index c183b81..ea5ce65 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -28,28 +28,6 @@
public final class NetworkConstants {
private NetworkConstants() { throw new RuntimeException("no instance permitted"); }
- /**
- * Ethernet constants.
- *
- * See also:
- * - https://tools.ietf.org/html/rfc894
- * - https://tools.ietf.org/html/rfc2464
- * - https://tools.ietf.org/html/rfc7042
- * - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml
- * - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
- */
- public static final int ETHER_DST_ADDR_OFFSET = 0;
- public static final int ETHER_SRC_ADDR_OFFSET = 6;
- public static final int ETHER_ADDR_LEN = 6;
-
- public static final int ETHER_TYPE_OFFSET = 12;
- public static final int ETHER_TYPE_LENGTH = 2;
- public static final int ETHER_TYPE_ARP = 0x0806;
- public static final int ETHER_TYPE_IPV4 = 0x0800;
- public static final int ETHER_TYPE_IPV6 = 0x86dd;
-
- public static final int ETHER_HEADER_LEN = 14;
-
public static final byte FF = asByte(0xff);
public static final byte[] ETHER_ADDR_BROADCAST = {
FF, FF, FF, FF, FF, FF
@@ -58,34 +36,12 @@
public static final int ETHER_MTU = 1500;
/**
- * ARP constants.
- *
- * See also:
- * - https://tools.ietf.org/html/rfc826
- * - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml
- */
- public static final int ARP_PAYLOAD_LEN = 28; // For Ethernet+IPv4.
- public static final int ARP_REQUEST = 1;
- public static final int ARP_REPLY = 2;
- public static final int ARP_HWTYPE_RESERVED_LO = 0;
- public static final int ARP_HWTYPE_ETHER = 1;
- public static final int ARP_HWTYPE_RESERVED_HI = 0xffff;
-
- /**
* IPv4 constants.
*
* See also:
* - https://tools.ietf.org/html/rfc791
*/
- public static final int IPV4_HEADER_MIN_LEN = 20;
- public static final int IPV4_IHL_MASK = 0xf;
- public static final int IPV4_FLAGS_OFFSET = 6;
- public static final int IPV4_FRAGMENT_MASK = 0x1fff;
- public static final int IPV4_PROTOCOL_OFFSET = 9;
- public static final int IPV4_SRC_ADDR_OFFSET = 12;
- public static final int IPV4_DST_ADDR_OFFSET = 16;
public static final int IPV4_ADDR_BITS = 32;
- public static final int IPV4_ADDR_LEN = 4;
/**
* IPv6 constants.
@@ -93,15 +49,10 @@
* See also:
* - https://tools.ietf.org/html/rfc2460
*/
- public static final int IPV6_HEADER_LEN = 40;
- public static final int IPV6_PROTOCOL_OFFSET = 6;
- public static final int IPV6_SRC_ADDR_OFFSET = 8;
- public static final int IPV6_DST_ADDR_OFFSET = 24;
public static final int IPV6_ADDR_BITS = 128;
public static final int IPV6_ADDR_LEN = 16;
public static final int IPV6_MIN_MTU = 1280;
public static final int RFC7421_PREFIX_LENGTH = 64;
- public static final int RFC6177_MIN_PREFIX_LENGTH = 48;
/**
* ICMP common (v4/v6) constants.
@@ -124,45 +75,7 @@
* - https://tools.ietf.org/html/rfc792
*/
public static final int ICMPV4_ECHO_REQUEST_TYPE = 8;
-
- /**
- * ICMPv6 constants.
- *
- * See also:
- * - https://tools.ietf.org/html/rfc4443
- * - https://tools.ietf.org/html/rfc4861
- */
- public static final int ICMPV6_HEADER_MIN_LEN = 4;
public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
- public static final int ICMPV6_ECHO_REPLY_TYPE = 129;
- public static final int ICMPV6_ROUTER_SOLICITATION = 133;
- public static final int ICMPV6_ROUTER_ADVERTISEMENT = 134;
- public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135;
- public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136;
-
- public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8;
- public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8;
- public static final int ICMPV6_ND_OPTION_SLLA = 1;
- public static final int ICMPV6_ND_OPTION_TLLA = 2;
- public static final int ICMPV6_ND_OPTION_MTU = 5;
-
-
- /**
- * UDP constants.
- *
- * See also:
- * - https://tools.ietf.org/html/rfc768
- */
- public static final int UDP_HEADER_LEN = 8;
-
- /**
- * DHCP(v4) constants.
- *
- * See also:
- * - https://tools.ietf.org/html/rfc2131
- */
- public static final int DHCP4_SERVER_PORT = 67;
- public static final int DHCP4_CLIENT_PORT = 68;
/**
* DNS constants.
@@ -176,9 +89,4 @@
* Utility functions.
*/
public static byte asByte(int i) { return (byte) i; }
-
- public static String asString(int i) { return Integer.toString(i); }
-
- public static int asUint(byte b) { return (b & 0xff); }
- public static int asUint(short s) { return (s & 0xffff); }
}
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 4811523..164570a 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -116,6 +116,7 @@
import com.android.server.backup.testing.TransportTestUtils.TransportMock;
import com.android.server.testing.shadows.FrameworkShadowLooper;
import com.android.server.testing.shadows.ShadowApplicationPackageManager;
+import com.android.server.testing.shadows.ShadowBackupActivityThread;
import com.android.server.testing.shadows.ShadowBackupDataInput;
import com.android.server.testing.shadows.ShadowBackupDataOutput;
import com.android.server.testing.shadows.ShadowEventLog;
@@ -163,6 +164,7 @@
ShadowBackupDataOutput.class,
ShadowEventLog.class,
ShadowQueuedWork.class,
+ ShadowBackupActivityThread.class,
})
@Presubmit
public class KeyValueBackupTaskTest {
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java b/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java
index aefc871..33b8aa7 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java
@@ -62,7 +62,7 @@
}
@Implementation
- protected static boolean appIsEligibleForBackup(ApplicationInfo app, PackageManager pm) {
+ protected static boolean appIsEligibleForBackup(ApplicationInfo app, int userId) {
return sAppsEligibleForBackup.contains(app.packageName);
}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupActivityThread.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupActivityThread.java
new file mode 100644
index 0000000..ca2e3b6
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupActivityThread.java
@@ -0,0 +1,79 @@
+/*
+ * 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.testing.shadows;
+
+import android.app.ActivityThread;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowActivityThread;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Extends the existing {@link ShadowActivityThread} to add support for
+ * {@link PackageManager#getApplicationEnabledSetting(String)} in the shadow {@link PackageManager}
+ * returned by {@link ShadowBackupActivityThread#getPackageManager()}.
+ */
+@Implements(value = ActivityThread.class, isInAndroidSdk = false, looseSignatures = true)
+public class ShadowBackupActivityThread extends ShadowActivityThread {
+ @Implementation
+ public static Object getPackageManager() {
+ ClassLoader classLoader = ShadowActivityThread.class.getClassLoader();
+ Class<?> iPackageManagerClass;
+ try {
+ iPackageManagerClass = classLoader.loadClass("android.content.pm.IPackageManager");
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ return Proxy.newProxyInstance(
+ classLoader,
+ new Class[] {iPackageManagerClass},
+ new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, @Nonnull Method method, Object[] args)
+ throws Exception {
+ if (method.getName().equals("getApplicationInfo")) {
+ String packageName = (String) args[0];
+ int flags = (Integer) args[1];
+
+ try {
+ return RuntimeEnvironment.application
+ .getPackageManager()
+ .getApplicationInfo(packageName, flags);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RemoteException(e.getMessage());
+ }
+ } else if (method.getName().equals("getApplicationEnabledSetting")) {
+ return 0;
+ } else {
+ return null;
+ }
+ }
+ });
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 6a153d5..6386b3b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -28,14 +28,18 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.AlarmManagerService.ACTIVE_INDEX;
+import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
+import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_PAROLE_CHANGED;
import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_LONG_TIME;
import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_SHORT_TIME;
-import static com.android.server.AlarmManagerService.Constants
- .KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
+import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
+import static com.android.server.AlarmManagerService.Constants.KEY_APP_STANDBY_QUOTAS_ENABLED;
import static com.android.server.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;
import static com.android.server.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
import static com.android.server.AlarmManagerService.Constants.KEY_MIN_FUTURITY;
import static com.android.server.AlarmManagerService.Constants.KEY_MIN_INTERVAL;
+import static com.android.server.AlarmManagerService.WORKING_INDEX;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -58,6 +62,7 @@
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
+import android.os.Message;
import android.os.PowerManager;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
@@ -65,7 +70,6 @@
import android.util.Log;
import android.util.SparseArray;
-import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.annotations.GuardedBy;
@@ -83,7 +87,6 @@
import java.util.ArrayList;
@Presubmit
-@SmallTest
@RunWith(AndroidJUnit4.class)
public class AlarmManagerServiceTest {
private static final String TAG = AlarmManagerServiceTest.class.getSimpleName();
@@ -91,7 +94,9 @@
private static final int SYSTEM_UI_UID = 123456789;
private static final int TEST_CALLING_UID = 12345;
+ private long mAppStandbyWindow;
private AlarmManagerService mService;
+ private UsageStatsManagerInternal.AppIdleStateChangeListener mAppStandbyListener;
@Mock
private ContentResolver mMockResolver;
@Mock
@@ -229,16 +234,23 @@
mService = new AlarmManagerService(mMockContext, mInjector);
spyOn(mService);
doNothing().when(mService).publishBinderService(any(), any());
- mService.onStart();
- mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
- spyOn(mService.mHandler);
- assertEquals(0, mService.mConstants.MIN_FUTURITY);
+ mService.onStart();
+ spyOn(mService.mHandler);
assertEquals(mService.mSystemUiUid, SYSTEM_UI_UID);
assertEquals(mService.mClockReceiver, mClockReceiver);
assertEquals(mService.mWakeLock, mWakeLock);
verify(mIActivityManager).registerUidObserver(any(IUidObserver.class), anyInt(), anyInt(),
isNull());
+
+ // Other boot phases don't matter
+ mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+ assertEquals(0, mService.mConstants.MIN_FUTURITY);
+ mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW;
+ ArgumentCaptor<UsageStatsManagerInternal.AppIdleStateChangeListener> captor =
+ ArgumentCaptor.forClass(UsageStatsManagerInternal.AppIdleStateChangeListener.class);
+ verify(mUsageStatsManagerInternal).addAppIdleStateChangeListener(captor.capture());
+ mAppStandbyListener = captor.getValue();
}
private void setTestAlarm(int type, long triggerTime, PendingIntent operation) {
@@ -254,6 +266,28 @@
return mockPi;
}
+ /**
+ * Careful while calling as this will replace any existing settings for the calling test.
+ */
+ private void setQuotasEnabled(boolean enabled) {
+ final StringBuilder constantsBuilder = new StringBuilder();
+ constantsBuilder.append(KEY_MIN_FUTURITY);
+ constantsBuilder.append("=0,");
+ // Capping active and working quotas to make testing feasible.
+ constantsBuilder.append(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
+ constantsBuilder.append("=8,");
+ constantsBuilder.append(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]);
+ constantsBuilder.append("=5,");
+ if (!enabled) {
+ constantsBuilder.append(KEY_APP_STANDBY_QUOTAS_ENABLED);
+ constantsBuilder.append("=false,");
+ }
+ doReturn(constantsBuilder.toString()).when(() -> Settings.Global.getString(mMockResolver,
+ Settings.Global.ALARM_MANAGER_CONSTANTS));
+ mService.mConstants.onChange(false, null);
+ assertEquals(mService.mConstants.APP_STANDBY_QUOTAS_ENABLED, enabled);
+ }
+
@Test
public void testSingleAlarmSet() {
final long triggerTime = mNowElapsedTest + 5000;
@@ -346,6 +380,7 @@
@Test
public void testStandbyBucketDelay_workingSet() throws Exception {
+ setQuotasEnabled(false);
setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent());
setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent());
assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed());
@@ -366,6 +401,7 @@
@Test
public void testStandbyBucketDelay_frequent() throws Exception {
+ setQuotasEnabled(false);
setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent());
setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent());
assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed());
@@ -385,6 +421,7 @@
@Test
public void testStandbyBucketDelay_rare() throws Exception {
+ setQuotasEnabled(false);
setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent());
setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent());
assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed());
@@ -402,6 +439,253 @@
assertEquals("Incorrect next alarm trigger.", expectedNextTrigger, mTestTimer.getElapsed());
}
+ private void testQuotasDeferralOnSet(int standbyBucket) throws Exception {
+ final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+ when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+ anyLong())).thenReturn(standbyBucket);
+ final long firstTrigger = mNowElapsedTest + 10;
+ for (int i = 0; i < quota; i++) {
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i,
+ getNewMockPendingIntent());
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+ }
+ // This one should get deferred on set
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10,
+ getNewMockPendingIntent());
+ final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow;
+ assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+ }
+
+ private void testQuotasDeferralOnExpiration(int standbyBucket) throws Exception {
+ final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+ when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+ anyLong())).thenReturn(standbyBucket);
+ final long firstTrigger = mNowElapsedTest + 10;
+ for (int i = 0; i < quota; i++) {
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i,
+ getNewMockPendingIntent());
+ }
+ // This one should get deferred after the latest alarm expires
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10,
+ getNewMockPendingIntent());
+ for (int i = 0; i < quota; i++) {
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+ }
+ final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow;
+ assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+ }
+
+ private void testQuotasNoDeferral(int standbyBucket) throws Exception {
+ final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+ when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+ anyLong())).thenReturn(standbyBucket);
+ final long firstTrigger = mNowElapsedTest + 10;
+ for (int i = 0; i < quota; i++) {
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i,
+ getNewMockPendingIntent());
+ }
+ // This delivery time maintains the quota invariant. Should not be deferred.
+ final long expectedNextTrigger = firstTrigger + mAppStandbyWindow + 5;
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger, getNewMockPendingIntent());
+ for (int i = 0; i < quota; i++) {
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+ }
+ assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+ }
+
+ @Test
+ public void testActiveQuota_deferredOnSet() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE);
+ }
+
+ @Test
+ public void testActiveQuota_deferredOnExpiration() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE);
+ }
+
+ @Test
+ public void testActiveQuota_notDeferred() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasNoDeferral(STANDBY_BUCKET_ACTIVE);
+ }
+
+ @Test
+ public void testWorkingQuota_deferredOnSet() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET);
+ }
+
+ @Test
+ public void testWorkingQuota_deferredOnExpiration() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET);
+ }
+
+ @Test
+ public void testWorkingQuota_notDeferred() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET);
+ }
+
+ @Test
+ public void testFrequentQuota_deferredOnSet() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT);
+ }
+
+ @Test
+ public void testFrequentQuota_deferredOnExpiration() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT);
+ }
+
+ @Test
+ public void testFrequentQuota_notDeferred() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasNoDeferral(STANDBY_BUCKET_FREQUENT);
+ }
+
+ @Test
+ public void testRareQuota_deferredOnSet() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnSet(STANDBY_BUCKET_RARE);
+ }
+
+ @Test
+ public void testRareQuota_deferredOnExpiration() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE);
+ }
+
+ @Test
+ public void testRareQuota_notDeferred() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasNoDeferral(STANDBY_BUCKET_RARE);
+ }
+
+ private void sendAndHandleBucketChanged(int bucket) {
+ when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+ anyLong())).thenReturn(bucket);
+ // Stubbing the handler call to simulate it synchronously here.
+ doReturn(true).when(mService.mHandler).sendMessage(any(Message.class));
+ mAppStandbyListener.onAppIdleStateChanged(TEST_CALLING_PACKAGE,
+ UserHandle.getUserId(TEST_CALLING_UID), false, bucket, 0);
+ final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+ verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture());
+ final Message lastMessage = messageCaptor.getValue();
+ assertEquals("Unexpected message send to handler", lastMessage.what,
+ APP_STANDBY_BUCKET_CHANGED);
+ mService.mHandler.handleMessage(lastMessage);
+ }
+
+ @Test
+ public void testQuotaDowngrade() throws Exception {
+ setQuotasEnabled(true);
+ final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET);
+ when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+ anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET);
+
+ final long firstTrigger = mNowElapsedTest + 10;
+ for (int i = 0; i < workingQuota; i++) {
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent());
+ }
+ // No deferrals now.
+ for (int i = 0; i < workingQuota - 1; i++) {
+ mNowElapsedTest = mTestTimer.getElapsed();
+ assertEquals(firstTrigger + i, mNowElapsedTest);
+ mTestTimer.expire();
+ }
+ // The next upcoming alarm in queue should also be set as expected.
+ assertEquals(firstTrigger + workingQuota - 1, mTestTimer.getElapsed());
+ // Downgrading the bucket now
+ sendAndHandleBucketChanged(STANDBY_BUCKET_RARE);
+ final int rareQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_RARE);
+ // The last alarm should now be deferred.
+ final long expectedNextTrigger = (firstTrigger + workingQuota - 1 - rareQuota)
+ + mAppStandbyWindow + 1;
+ assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+ }
+
+ @Test
+ public void testQuotaUpgrade() throws Exception {
+ setQuotasEnabled(true);
+ final int frequentQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_FREQUENT);
+ when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+ anyLong())).thenReturn(STANDBY_BUCKET_FREQUENT);
+
+ final long firstTrigger = mNowElapsedTest + 10;
+ for (int i = 0; i < frequentQuota + 1; i++) {
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent());
+ if (i < frequentQuota) {
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+ }
+ }
+ // The last alarm should be deferred due to exceeding the quota
+ final long deferredTrigger = firstTrigger + 1 + mAppStandbyWindow;
+ assertEquals(deferredTrigger, mTestTimer.getElapsed());
+
+ // Upgrading the bucket now
+ sendAndHandleBucketChanged(STANDBY_BUCKET_ACTIVE);
+ // The last alarm should now be rescheduled to go as per original expectations
+ final long originalTrigger = firstTrigger + frequentQuota;
+ assertEquals("Incorrect next alarm trigger", originalTrigger, mTestTimer.getElapsed());
+ }
+
+ private void sendAndHandleParoleChanged(boolean parole) {
+ // Stubbing the handler call to simulate it synchronously here.
+ doReturn(true).when(mService.mHandler).sendMessage(any(Message.class));
+ mAppStandbyListener.onParoleStateChanged(parole);
+ final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+ verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture());
+ final Message lastMessage = messageCaptor.getValue();
+ assertEquals("Unexpected message send to handler", lastMessage.what,
+ APP_STANDBY_PAROLE_CHANGED);
+ mService.mHandler.handleMessage(lastMessage);
+ }
+
+ @Test
+ public void testParole() throws Exception {
+ setQuotasEnabled(true);
+ final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET);
+ when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+ anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET);
+
+ final long firstTrigger = mNowElapsedTest + 10;
+ final int totalAlarms = workingQuota + 10;
+ for (int i = 0; i < totalAlarms; i++) {
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent());
+ }
+ // Use up the quota, no deferrals expected.
+ for (int i = 0; i < workingQuota; i++) {
+ mNowElapsedTest = mTestTimer.getElapsed();
+ assertEquals(firstTrigger + i, mNowElapsedTest);
+ mTestTimer.expire();
+ }
+ // Any subsequent alarms in queue should all be deferred
+ assertEquals(firstTrigger + mAppStandbyWindow + 1, mTestTimer.getElapsed());
+ // Paroling now
+ sendAndHandleParoleChanged(true);
+
+ // Subsequent alarms should now go off as per original expectations.
+ for (int i = 0; i < 5; i++) {
+ mNowElapsedTest = mTestTimer.getElapsed();
+ assertEquals(firstTrigger + workingQuota + i, mNowElapsedTest);
+ mTestTimer.expire();
+ }
+ // Come out of parole
+ sendAndHandleParoleChanged(false);
+
+ // Subsequent alarms should again get deferred
+ final long expectedNextTrigger = (firstTrigger + 5) + 1 + mAppStandbyWindow;
+ assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+ }
+
@Test
public void testAlarmRestrictedInBatterSaver() throws Exception {
final ArgumentCaptor<AppStateTracker.Listener> listenerArgumentCaptor =
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 1a16e56..53d72bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -1376,6 +1376,45 @@
verifyLightStateConditions(LIGHT_STATE_ACTIVE);
}
+ @Test
+ public void testStepToIdleMode() {
+ float delta = mDeviceIdleController.MIN_PRE_IDLE_FACTOR_CHANGE;
+ for (int mode = PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL;
+ mode <= PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG;
+ mode++) {
+ int ret = mDeviceIdleController.setPreIdleTimeoutMode(mode);
+ if (mode == PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL) {
+ assertEquals("setPreIdleTimeoutMode: " + mode + " failed.",
+ mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret);
+ } else {
+ assertEquals("setPreIdleTimeoutMode: " + mode + " failed.",
+ mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret);
+ }
+ //TODO(b/123045185): Mocked Handler of DeviceIdleController to make message loop
+ //workable in this test class
+ mDeviceIdleController.updatePreIdleFactor();
+ float expectedfactor = mDeviceIdleController.getPreIdleTimeoutByMode(mode);
+ float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor();
+ assertEquals("Pre idle time factor of mode [" + mode + "].",
+ expectedfactor, curfactor, delta);
+ mDeviceIdleController.resetPreIdleTimeoutMode();
+ mDeviceIdleController.updatePreIdleFactor();
+
+ checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_INACTIVE);
+ checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_PENDING);
+
+ checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_SENSING);
+ checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_LOCATING);
+ checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_QUICK_DOZE_DELAY);
+ checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_MAINTENANCE);
+ checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE);
+ checkMaybeDoAnImmediateMaintenance(expectedfactor);
+ }
+ float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor();
+ assertEquals("Pre idle time factor of mode default.",
+ 1.0f, curfactor, delta);
+ }
+
private void enterDeepState(int state) {
switch (state) {
case STATE_ACTIVE:
@@ -1599,4 +1638,84 @@
fail("Conditions for " + lightStateToString(expectedLightState) + " unknown.");
}
}
+
+ private void checkNextAlarmTimeWithNewPreIdleFactor(float factor, int state) {
+ final long errorTolerance = 1000;
+ enterDeepState(state);
+ long now = SystemClock.elapsedRealtime();
+ long alarm = mDeviceIdleController.getNextAlarmTime();
+ if (state == STATE_INACTIVE || state == STATE_IDLE_PENDING) {
+ int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+ if (Float.compare(factor, 1.0f) == 0) {
+ assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+ mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret);
+ } else {
+ assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+ mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret);
+ }
+ if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) {
+ mDeviceIdleController.updatePreIdleFactor();
+ long newAlarm = mDeviceIdleController.getNextAlarmTime();
+ long newDelay = (long) ((alarm - now) * factor);
+ assertTrue("setPreIdleTimeoutFactor: " + factor,
+ Math.abs(newDelay - (newAlarm - now)) < errorTolerance);
+ mDeviceIdleController.resetPreIdleTimeoutMode();
+ mDeviceIdleController.updatePreIdleFactor();
+ mDeviceIdleController.maybeDoImmediateMaintenance();
+ newAlarm = mDeviceIdleController.getNextAlarmTime();
+ assertTrue("resetPreIdleTimeoutMode from: " + factor,
+ Math.abs(newAlarm - alarm) < errorTolerance);
+ mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+ now = SystemClock.elapsedRealtime();
+ enterDeepState(state);
+ newAlarm = mDeviceIdleController.getNextAlarmTime();
+ assertTrue("setPreIdleTimeoutFactor: " + factor + " before step to idle",
+ Math.abs(newDelay - (newAlarm - now)) < errorTolerance);
+ mDeviceIdleController.resetPreIdleTimeoutMode();
+ mDeviceIdleController.updatePreIdleFactor();
+ mDeviceIdleController.maybeDoImmediateMaintenance();
+ }
+ } else {
+ mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+ mDeviceIdleController.updatePreIdleFactor();
+ long newAlarm = mDeviceIdleController.getNextAlarmTime();
+ assertTrue("setPreIdleTimeoutFactor: " + factor
+ + " shounld not change next alarm" ,
+ (newAlarm == alarm));
+ mDeviceIdleController.resetPreIdleTimeoutMode();
+ mDeviceIdleController.updatePreIdleFactor();
+ mDeviceIdleController.maybeDoImmediateMaintenance();
+ }
+ }
+
+ private void checkMaybeDoAnImmediateMaintenance(float factor) {
+ int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+ final long minuteInMillis = 60 * 1000;
+ if (Float.compare(factor, 1.0f) == 0) {
+ assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+ mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret);
+ } else {
+ assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+ mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret);
+ }
+ if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) {
+ enterDeepState(STATE_IDLE);
+ long now = SystemClock.elapsedRealtime();
+ long alarm = mDeviceIdleController.getNextAlarmTime();
+ mDeviceIdleController.setIdleStartTimeForTest(
+ now - (long) (mConstants.IDLE_TIMEOUT * 0.6));
+ mDeviceIdleController.maybeDoImmediateMaintenance();
+ long newAlarm = mDeviceIdleController.getNextAlarmTime();
+ assertTrue("maintenance not reschedule IDLE_TIMEOUT * 0.6",
+ newAlarm == alarm);
+ mDeviceIdleController.setIdleStartTimeForTest(
+ now - (long) (mConstants.IDLE_TIMEOUT * 1.2));
+ mDeviceIdleController.maybeDoImmediateMaintenance();
+ newAlarm = mDeviceIdleController.getNextAlarmTime();
+ assertTrue("maintenance not reschedule IDLE_TIMEOUT * 1.2",
+ (newAlarm - now) < minuteInMillis);
+ mDeviceIdleController.resetPreIdleTimeoutMode();
+ mDeviceIdleController.updatePreIdleFactor();
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 57ee6dc..cad71a2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -25,6 +25,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
+import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
@@ -370,16 +371,19 @@
mQuotaController.saveTimingSession(0, "com.android.test.stay", one);
ExecutionStats expectedStats = new ExecutionStats();
- expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
- mQuotaController.onAppRemovedLocked("com.android.test.remove", 10001);
+ final int uid = 10001;
+ mQuotaController.onAppRemovedLocked("com.android.test.remove", uid);
assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove"));
assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay"));
assertEquals(expectedStats,
mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX));
assertNotEquals(expectedStats,
mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX));
+
+ assertFalse(mQuotaController.getForegroundUids().get(uid));
}
@Test
@@ -405,7 +409,7 @@
mQuotaController.saveTimingSession(10, "com.android.test", one);
ExecutionStats expectedStats = new ExecutionStats();
- expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
mQuotaController.onUserRemovedLocked(0);
@@ -440,14 +444,14 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
// Invalid time is now +24 hours since there are no sessions at all for the app.
- expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
assertEquals(expectedStats, inputStats);
inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
// Invalid time is now +18 hours since there are no sessions in the window but the earliest
// session is 6 hours ago.
- expectedStats.invalidTimeElapsed = now + 18 * HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 18 * HOUR_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 0;
expectedStats.bgJobCountInWindow = 0;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -457,7 +461,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
// Invalid time is now since the session straddles the window cutoff time.
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 3;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -468,7 +472,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS;
// Invalid time is now since the start of the session is at the very edge of the window
// cutoff time.
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 3;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -479,7 +483,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
// Invalid time is now +44 minutes since the earliest session in the window is now-5
// minutes.
- expectedStats.invalidTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 3;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -489,7 +493,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
// Invalid time is now since the session is at the very edge of the window cutoff time.
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 4;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -500,7 +504,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
// Invalid time is now since the start of the session is at the very edge of the window
// cutoff time.
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 5;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -510,7 +514,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
// Invalid time is now since the session straddles the window cutoff time.
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 10;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -523,7 +527,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS;
// Invalid time is now +59 minutes since the earliest session in the window is now-121
// minutes.
- expectedStats.invalidTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 10;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -536,7 +540,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
// Invalid time is now since the start of the session is at the very edge of the window
// cutoff time.
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 15;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -546,14 +550,14 @@
mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
assertEquals(expectedStats, inputStats);
- // Make sure invalidTimeElapsed is set correctly when it's dependent on the max period.
+ // Make sure expirationTimeElapsed is set correctly when it's dependent on the max period.
mQuotaController.getTimingSessions(0, "com.android.test")
.add(0,
createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
// Invalid time is now +1 hour since the earliest session in the max period is 1 hour
// before the end of the max period cutoff time.
- expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 15;
expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
@@ -569,7 +573,7 @@
2 * MINUTE_IN_MILLIS, 2));
inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
// Invalid time is now since the earlist session straddles the max period cutoff time.
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 15;
expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
@@ -599,7 +603,7 @@
// Active
expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
- expectedStats.invalidTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 5;
expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -609,7 +613,7 @@
// Working
expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 10;
expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -621,7 +625,7 @@
// Frequent
expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
- expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 15;
expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -633,7 +637,7 @@
// Rare
expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
- expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 20;
expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -675,7 +679,7 @@
ExecutionStats expectedStats = new ExecutionStats();
expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
- expectedStats.invalidTimeElapsed = originalStatsActive.invalidTimeElapsed;
+ expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed;
expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
@@ -688,7 +692,7 @@
assertEquals(expectedStats, newStatsActive);
expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
- expectedStats.invalidTimeElapsed = originalStatsWorking.invalidTimeElapsed;
+ expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed;
expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed;
@@ -698,7 +702,7 @@
assertNotEquals(expectedStats, newStatsWorking);
expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
- expectedStats.invalidTimeElapsed = originalStatsFrequent.invalidTimeElapsed;
+ expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed;
expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed;
@@ -708,7 +712,7 @@
assertNotEquals(expectedStats, newStatsFrequent);
expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
- expectedStats.invalidTimeElapsed = originalStatsRare.invalidTimeElapsed;
+ expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed;
expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed;
@@ -719,6 +723,77 @@
}
@Test
+ public void testIsWithinQuotaLocked_NeverApp() {
+ assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX));
+ }
+
+ @Test
+ public void testIsWithinQuotaLocked_Charging() {
+ setCharging();
+ assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
+ }
+
+ @Test
+ public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount() {
+ setDischarging();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.incrementJobCount(0, "com.android.test", 5);
+ assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+ }
+
+ @Test
+ public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
+ setDischarging();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
+ mQuotaController.saveTimingSession(0, "com.android.test.spam",
+ createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
+ mQuotaController.saveTimingSession(0, "com.android.test.spam",
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
+ mQuotaController.incrementJobCount(0, "com.android.test.spam", jobCount);
+ assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.spam",
+ WORKING_INDEX));
+
+ mQuotaController.saveTimingSession(0, "com.android.test.frequent",
+ createTimingSession(now - (2 * HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 2000));
+ mQuotaController.saveTimingSession(0, "com.android.test.frequent",
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500));
+ assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.frequent",
+ FREQUENT_INDEX));
+ }
+
+ @Test
+ public void testIsWithinQuotaLocked_OverDuration_UnderJobCount() {
+ setDischarging();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.incrementJobCount(0, "com.android.test", 5);
+ assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+ }
+
+ @Test
+ public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
+ setDischarging();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
+ mQuotaController.incrementJobCount(0, "com.android.test", jobCount);
+ assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+ }
+
+ @Test
public void testMaybeScheduleCleanupAlarmLocked() {
// No sessions saved yet.
mQuotaController.maybeScheduleCleanupAlarmLocked();
@@ -752,6 +827,7 @@
// Active window size is 10 minutes.
final int standbyBucket = ACTIVE_INDEX;
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
// No sessions saved yet.
mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -1016,11 +1092,37 @@
.set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
- inOrder.verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
- any());
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class));
}
+ @Test
+ public void testMaybeScheduleStartAlarmLocked_JobCount_AllowedTime() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final int standbyBucket = WORKING_INDEX;
+ ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
+ SOURCE_PACKAGE, standbyBucket);
+ stats.jobCountInAllowedTime =
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME + 2;
+
+ // Invalid time in the past, so the count shouldn't be used.
+ stats.jobCountExpirationTimeElapsed =
+ now - mQuotaController.getAllowedTimePerPeriodMs() / 2;
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // Invalid time in the future, so the count should be used.
+ stats.jobCountExpirationTimeElapsed =
+ now + mQuotaController.getAllowedTimePerPeriodMs() / 2;
+ final long expectedWorkingAlarmTime =
+ stats.jobCountExpirationTimeElapsed + mQuotaController.getAllowedTimePerPeriodMs();
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
+ verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+ }
/**
* Tests that the start alarm is properly rescheduled if the earliest session that contributes
@@ -1172,6 +1274,11 @@
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = 5000;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 4000;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 3000;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 2000;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 500;
mQuotaController.onConstantsUpdatedLocked();
@@ -1183,11 +1290,16 @@
mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
+ assertEquals(500, mQuotaController.getMaxJobCountPerAllowedTime());
+ assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
+ assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
+ assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
+ assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
}
@Test
public void testConstantsUpdating_InvalidValues() {
- // Test negatives
+ // Test negatives/too low.
mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS;
@@ -1195,6 +1307,11 @@
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = -1;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 1;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 1;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 1;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 0;
mQuotaController.onConstantsUpdatedLocked();
@@ -1205,6 +1322,11 @@
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
+ assertEquals(10, mQuotaController.getMaxJobCountPerAllowedTime());
+ assertEquals(100, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
+ assertEquals(100, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
+ assertEquals(100, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
+ assertEquals(100, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
// Test larger than a day. Controller should cap at one day.
mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS;
@@ -1246,6 +1368,7 @@
@Test
public void testTimerTracking_Discharging() {
setDischarging();
+ setProcessState(ActivityManager.PROCESS_STATE_BACKUP);
JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1);
mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
@@ -1293,6 +1416,8 @@
*/
@Test
public void testTimerTracking_ChargingAndDischarging() {
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1);
mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2);
@@ -1363,6 +1488,7 @@
@Test
public void testTimerTracking_AllBackground() {
setDischarging();
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1);
mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
@@ -1503,6 +1629,64 @@
}
/**
+ * Tests that Timers don't track job counts while in the foreground.
+ */
+ @Test
+ public void testTimerTracking_JobCount_Foreground() {
+ setDischarging();
+
+ final int standbyBucket = ACTIVE_INDEX;
+ JobStatus jobFg1 = createJobStatus("testTimerTracking_JobCount_Foreground", 1);
+ JobStatus jobFg2 = createJobStatus("testTimerTracking_JobCount_Foreground", 2);
+
+ mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobFg2, null);
+ assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
+ SOURCE_PACKAGE, standbyBucket);
+ assertEquals(0, stats.jobCountInAllowedTime);
+
+ setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ mQuotaController.prepareForExecutionLocked(jobFg1);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.prepareForExecutionLocked(jobFg2);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false);
+ assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ assertEquals(0, stats.jobCountInAllowedTime);
+ }
+
+ /**
+ * Tests that Timers properly track job counts while in the background.
+ */
+ @Test
+ public void testTimerTracking_JobCount_Background() {
+ final int standbyBucket = WORKING_INDEX;
+ JobStatus jobBg1 = createJobStatus("testTimerTracking_JobCount_Background", 1);
+ JobStatus jobBg2 = createJobStatus("testTimerTracking_JobCount_Background", 2);
+ mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+
+ ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
+ SOURCE_PACKAGE, standbyBucket);
+ assertEquals(0, stats.jobCountInAllowedTime);
+
+ setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+ mQuotaController.prepareForExecutionLocked(jobBg1);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.prepareForExecutionLocked(jobBg2);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobBg1, null, false);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+
+ assertEquals(2, stats.jobCountInAllowedTime);
+ }
+
+ /**
* Tests that Timers properly track overlapping top and background jobs.
*/
@Test
@@ -1680,6 +1864,7 @@
JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
+ setProcessState(ActivityManager.PROCESS_STATE_HOME);
// Now the package only has two seconds to run.
final long remainingTimeMs = 2 * SECOND_IN_MILLIS;
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -1707,6 +1892,7 @@
JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
Handler handler = mQuotaController.getHandler();
spyOn(handler);
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
similarity index 73%
rename from services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
rename to services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
index f31ca55..5009d64 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
@@ -19,51 +19,41 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.app.NotificationManager;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.res.Resources;
import android.provider.Settings.Global;
-import android.test.mock.MockContext;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.google.common.base.Objects;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.HashMap;
+import java.util.Objects;
/**
- atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
+ * atest com.android.server.power.batterysaver.BatterySaverStateMachineTest
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class BatterySaverStateMachineTest {
- private MyMockContext mMockContext;
+ private Context mMockContext;
private ContentResolver mMockContextResolver;
private BatterySaverController mMockBatterySaverController;
+ private NotificationManager mMockNotificationManager;
private Device mDevice;
private TestableBatterySaverStateMachine mTarget;
private Resources mMockResources;
- private class MyMockContext extends MockContext {
- @Override
- public ContentResolver getContentResolver() {
- return mMockContextResolver;
- }
-
- @Override
- public Resources getResources() {
- return mMockResources;
- }
- }
-
private DevicePersistedState mPersistedState;
private class DevicePersistedState {
@@ -116,6 +106,10 @@
mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE, 0) != 0,
mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE_STICKY, 0) != 0,
mDevice.getLowPowerModeTriggerLevel(),
+ mPersistedState.global.getOrDefault(
+ Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 0) != 0,
+ mPersistedState.global.getOrDefault(
+ Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90),
mPersistedState.global.getOrDefault(Global.AUTOMATIC_POWER_SAVER_MODE, 0),
mPersistedState.global.getOrDefault(
Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0) != 0,
@@ -137,13 +131,13 @@
* Test target class.
*/
private class TestableBatterySaverStateMachine extends BatterySaverStateMachine {
- public TestableBatterySaverStateMachine() {
+ TestableBatterySaverStateMachine() {
super(new Object(), mMockContext, mMockBatterySaverController);
}
@Override
protected void putGlobalSetting(String key, int value) {
- if (Objects.equal(mPersistedState.global.get(key), value)) {
+ if (Objects.equals(mPersistedState.global.get(key), value)) {
return;
}
mDevice.putGlobalSetting(key, value);
@@ -163,15 +157,25 @@
void runOnBgThreadLazy(Runnable r, int delayMillis) {
r.run();
}
+
+ @Override
+ void triggerDynamicModeNotification() {
+ // Do nothing
+ }
}
@Before
public void setUp() {
- mMockContext = new MyMockContext();
+ mMockContext = mock(Context.class);
mMockContextResolver = mock(ContentResolver.class);
mMockBatterySaverController = mock(BatterySaverController.class);
+ mMockNotificationManager = mock(NotificationManager.class);
mMockResources = mock(Resources.class);
+ doReturn(mMockContextResolver).when(mMockContext).getContentResolver();
+ doReturn(mMockResources).when(mMockContext).getResources();
+ doReturn(mMockNotificationManager).when(mMockContext)
+ .getSystemService(NotificationManager.class);
doAnswer((inv) -> mDevice.batterySaverEnabled = inv.getArgument(0))
.when(mMockBatterySaverController).enableBatterySaver(anyBoolean(), anyInt());
when(mMockBatterySaverController.isEnabled())
@@ -446,8 +450,9 @@
}
@Test
- public void testAutoBatterySaver_withSticky() {
+ public void testAutoBatterySaver_withSticky_withAutoOffDisabled() {
mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50);
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 0);
mTarget.setBatterySaverEnabledManually(true);
@@ -518,6 +523,197 @@
}
@Test
+ public void testAutoBatterySaver_withSticky_withAutoOffEnabled() {
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50);
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 1);
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90);
+
+ // Scenario 1: User turns BS on manually above the threshold, it shouldn't turn off even
+ // with battery level change above threshold.
+ mDevice.setBatteryLevel(100);
+ mTarget.setBatterySaverEnabledManually(true);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(100, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(95);
+
+ assertEquals(true, mDevice.batterySaverEnabled); // Stays on.
+ assertEquals(95, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 2: User turns BS on manually above the threshold then charges device. BS
+ // shouldn't turn back on.
+ mDevice.setPowered(true);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(95, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(97);
+ mDevice.setPowered(false);
+
+ assertEquals(false, mDevice.batterySaverEnabled); // Sticky BS no longer enabled.
+ assertEquals(97, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 3: User turns BS on manually above the threshold. Device drains below
+ // threshold and then charged to below threshold. Sticky BS should activate.
+ mTarget.setBatterySaverEnabledManually(true);
+ mDevice.setBatteryLevel(30);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(30, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setPowered(true);
+ mDevice.setBatteryLevel(80);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(80, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setPowered(false);
+
+ assertEquals(true, mDevice.batterySaverEnabled); // Still enabled.
+ assertEquals(80, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(30);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(30, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ // Scenario 4: User turns BS on manually above the threshold. Device drains below
+ // threshold and is eventually charged to above threshold. Sticky BS should turn off.
+ mDevice.setPowered(true);
+ mDevice.setBatteryLevel(90);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setPowered(false);
+
+ assertEquals(false, mDevice.batterySaverEnabled); // Sticky BS no longer enabled.
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 5: User turns BS on manually below threshold and charges to below threshold.
+ // Sticky BS should activate.
+ mDevice.setBatteryLevel(70);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(70, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mTarget.setBatterySaverEnabledManually(true);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(70, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setPowered(true);
+ mDevice.setBatteryLevel(80);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(80, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setPowered(false);
+
+ assertEquals(true, mDevice.batterySaverEnabled); // Sticky BS still on.
+ assertEquals(80, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 6: User turns BS on manually below threshold and eventually charges to above
+ // threshold. Sticky BS should turn off.
+
+ mDevice.setPowered(true);
+ mDevice.setBatteryLevel(95);
+ mDevice.setPowered(false);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(95, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 7: User turns BS on above threshold and then reboots device. Sticky BS
+ // shouldn't activate.
+ mTarget.setBatterySaverEnabledManually(true);
+ mPersistedState.batteryLevel = 93;
+
+ initDevice();
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(93, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 8: User turns BS on below threshold and then reboots device without charging.
+ // Sticky BS should activate.
+ mDevice.setBatteryLevel(75);
+ mTarget.setBatterySaverEnabledManually(true);
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(75, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ initDevice();
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(75, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 9: User turns BS on below threshold and then reboots device after charging
+ // above threshold. Sticky BS shouldn't activate.
+ mDevice.setBatteryLevel(80);
+ mTarget.setBatterySaverEnabledManually(true);
+ mPersistedState.batteryLevel = 100;
+
+ initDevice();
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(100, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 10: Somehow autoDisableLevel is set to a value below lowPowerModeTriggerLevel
+ // and then user enables manually above both thresholds, discharges below
+ // autoDisableLevel and then charges up to between autoDisableLevel and
+ // lowPowerModeTriggerLevel. Sticky BS shouldn't activate, but BS should still be on
+ // because the level is below lowPowerModeTriggerLevel.
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 75);
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 1);
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 60);
+ initDevice();
+
+ mDevice.setBatteryLevel(90);
+ mTarget.setBatterySaverEnabledManually(true);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(50);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(50, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setPowered(true);
+ mDevice.setBatteryLevel(65);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(65, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setPowered(false);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(65, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+ }
+
+ @Test
public void testAutoBatterySaver_withStickyDisabled() {
when(mMockResources.getBoolean(
com.android.internal.R.bool.config_batterySaverStickyBehaviourDisabled))
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 1b5ba26..dc31c0f 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -156,6 +156,7 @@
</activity>
<activity android:name="com.android.server.accounts.AccountAuthenticatorDummyActivity" />
+ <activity android:name="com.android.server.adb.AdbDebuggingManagerTestActivity" />
<activity-alias android:name="a.ShortcutEnabled"
android:targetActivity="com.android.server.pm.ShortcutTestActivity"
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
new file mode 100644
index 0000000..782dc3e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+import static android.view.WindowManagerPolicyConstants.FLAG_PASS_TO_USER;
+
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for AccessibilityInputFilterTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityInputFilterTest {
+ private static int sNextDisplayId = DEFAULT_DISPLAY;
+ private static final int SECOND_DISPLAY = DEFAULT_DISPLAY + 1;
+ private static final float DEFAULT_X = 100f;
+ private static final float DEFAULT_Y = 100f;
+
+ private final SparseArray<EventStreamTransformation> mEventHandler = new SparseArray<>(0);
+ private final ArrayList<Display> mDisplayList = new ArrayList<>();
+ private final int mFeatures = FLAG_FEATURE_AUTOCLICK
+ | FLAG_FEATURE_TOUCH_EXPLORATION
+ | FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER
+ | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
+ | FLAG_FEATURE_INJECT_MOTION_EVENTS
+ | FLAG_FEATURE_FILTER_KEY_EVENTS;
+
+ // The expected order of EventStreamTransformations.
+ private final Class[] mExpectedEventHandlerTypes =
+ {KeyboardInterceptor.class, MotionEventInjector.class,
+ MagnificationGestureHandler.class, TouchExplorer.class,
+ AutoclickController.class, AccessibilityInputFilter.class};
+
+ private MagnificationController mMockMagnificationController;
+ private AccessibilityManagerService mAms;
+ private AccessibilityInputFilter mA11yInputFilter;
+ private EventCaptor mCaptor1;
+ private EventCaptor mCaptor2;
+ private long mLastDownTime = Integer.MIN_VALUE;
+
+ private class EventCaptor implements EventStreamTransformation {
+ List<InputEvent> mEvents = new ArrayList<>();
+
+ @Override
+ public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mEvents.add(event.copy());
+ }
+
+ @Override
+ public void onKeyEvent(KeyEvent event, int policyFlags) {
+ mEvents.add(event.copy());
+ }
+
+ @Override
+ public void setNext(EventStreamTransformation next) {
+ }
+
+ @Override
+ public EventStreamTransformation getNext() {
+ return null;
+ }
+
+ @Override
+ public void clearEvents(int inputSource) {
+ clear();
+ }
+
+ private void clear() {
+ mEvents.clear();
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ Context context = InstrumentationRegistry.getContext();
+
+ setDisplayCount(1);
+ mAms = spy(new AccessibilityManagerService(context));
+ mMockMagnificationController = mock(MagnificationController.class);
+ mA11yInputFilter = new AccessibilityInputFilter(context, mAms, mEventHandler);
+ mA11yInputFilter.onInstalled();
+
+ when(mAms.getValidDisplayList()).thenReturn(mDisplayList);
+ when(mAms.getMagnificationController()).thenReturn(mMockMagnificationController);
+ }
+
+ @After
+ public void tearDown() {
+ mA11yInputFilter.onUninstalled();
+ }
+
+ @Test
+ public void testEventHandler_shouldChangeAfterSetUserAndEnabledFeatures() {
+ prepareLooper();
+
+ // Check if there is no mEventHandler when no feature is set.
+ assertEquals(0, mEventHandler.size());
+
+ // Check if mEventHandler is added/removed after setting a11y features.
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+ assertEquals(1, mEventHandler.size());
+
+ mA11yInputFilter.setUserAndEnabledFeatures(0, 0);
+ assertEquals(0, mEventHandler.size());
+ }
+
+ @Test
+ public void testEventHandler_shouldChangeAfterOnDisplayChanged() {
+ prepareLooper();
+
+ // Check if there is only one mEventHandler when there is one default display.
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+ assertEquals(1, mEventHandler.size());
+
+ // Check if it has correct numbers of mEventHandler for corresponding displays.
+ setDisplayCount(4);
+ mA11yInputFilter.onDisplayChanged();
+ assertEquals(4, mEventHandler.size());
+
+ setDisplayCount(2);
+ mA11yInputFilter.onDisplayChanged();
+ assertEquals(2, mEventHandler.size());
+ }
+
+ @Test
+ public void testEventHandler_shouldHaveCorrectOrderForEventStreamTransformation() {
+ prepareLooper();
+
+ setDisplayCount(2);
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+ assertEquals(2, mEventHandler.size());
+
+ // Check if mEventHandler for each display has correct order of the
+ // EventStreamTransformations.
+ EventStreamTransformation next = mEventHandler.get(DEFAULT_DISPLAY);
+ for (int i = 0; next != null; i++) {
+ assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]);
+ next = next.getNext();
+ }
+
+ next = mEventHandler.get(SECOND_DISPLAY);
+ // Start from index 1 because KeyboardInterceptor only exists in EventHandler for
+ // DEFAULT_DISPLAY.
+ for (int i = 1; next != null; i++) {
+ assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]);
+ next = next.getNext();
+ }
+ }
+
+ @Test
+ public void testInputEvent_shouldDispatchToCorrespondingEventHandlers() {
+ prepareLooper();
+
+ setDisplayCount(2);
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+ assertEquals(2, mEventHandler.size());
+
+ mCaptor1 = new EventCaptor();
+ mCaptor2 = new EventCaptor();
+ mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
+ mEventHandler.put(SECOND_DISPLAY, mCaptor2);
+
+ // InputEvent with different displayId should be dispatched to corresponding EventHandler.
+ send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+ send(downEvent(SECOND_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+
+ assertEquals(1, mCaptor1.mEvents.size());
+ assertEquals(1, mCaptor2.mEvents.size());
+ }
+
+ @Test
+ public void testInputEvent_shouldClearEventsForAllEventHandlers() {
+ prepareLooper();
+
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+ assertEquals(1, mEventHandler.size());
+
+ mCaptor1 = new EventCaptor();
+ mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
+
+ send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+ send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+ assertEquals(2, mCaptor1.mEvents.size());
+
+ // InputEvent with different input source should trigger clearEvents() for each
+ // EventStreamTransformation in EventHandler.
+ send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_MOUSE));
+ assertEquals(1, mCaptor1.mEvents.size());
+ }
+
+ private static void prepareLooper() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ private Display createStubDisplay(DisplayInfo displayInfo) {
+ final int displayId = sNextDisplayId++;
+ final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
+ displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
+ return display;
+ }
+
+ private void setDisplayCount(int count) {
+ sNextDisplayId = DEFAULT_DISPLAY;
+ mDisplayList.clear();
+ for (int i = 0; i < count; i++) {
+ mDisplayList.add(createStubDisplay(new DisplayInfo()));
+ }
+ }
+
+ private void send(InputEvent event) {
+ mA11yInputFilter.onInputEvent(event, /* policyFlags */ FLAG_PASS_TO_USER);
+ }
+
+ private MotionEvent downEvent(int displayId, int source) {
+ mLastDownTime = SystemClock.uptimeMillis();
+ final MotionEvent ev = MotionEvent.obtain(mLastDownTime, mLastDownTime,
+ MotionEvent.ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0);
+ ev.setDisplayId(displayId);
+ ev.setSource(source);
+ return ev;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
index feffeef..773b877 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
@@ -20,7 +20,9 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
@@ -36,19 +38,15 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
-import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
-import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
import android.view.MagnificationSpec;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.R;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
@@ -81,48 +79,36 @@
static final Region OTHER_REGION = new Region(OTHER_MAGNIFICATION_BOUNDS);
static final int SERVICE_ID_1 = 1;
static final int SERVICE_ID_2 = 2;
+ static final int DISPLAY_0 = 0;
+ static final int DISPLAY_1 = 1;
+ static final int DISPLAY_COUNT = 2;
+ static final int INVALID_DISPLAY = 2;
+ final MagnificationController.ControllerContext mMockControllerCtx =
+ mock(MagnificationController.ControllerContext.class);
final Context mMockContext = mock(Context.class);
final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
- final MessageCapturingHandler mMessageCapturingHandler =
- new MessageCapturingHandler(null);
+ final MessageCapturingHandler mMessageCapturingHandler = new MessageCapturingHandler(null);
- final ValueAnimator mMockValueAnimator = mock(ValueAnimator.class);
- MagnificationController.SettingsBridge mMockSettingsBridge;
+ ValueAnimator mMockValueAnimator;
+ ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
MagnificationController mMagnificationController;
- ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
@Before
public void setUp() {
Looper looper = InstrumentationRegistry.getContext().getMainLooper();
// Pretending ID of the Thread associated with looper as main thread ID in controller
when(mMockContext.getMainLooper()).thenReturn(looper);
- Resources mockResources = mock(Resources.class);
- when(mMockContext.getResources()).thenReturn(mockResources);
- when(mockResources.getInteger(R.integer.config_longAnimTime))
- .thenReturn(1000);
- mMockSettingsBridge = mock(MagnificationController.SettingsBridge.class);
- mMagnificationController = new MagnificationController(mMockContext, mMockAms, new Object(),
- mMessageCapturingHandler, mMockWindowManager, mMockValueAnimator,
- mMockSettingsBridge);
+ when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
+ when(mMockControllerCtx.getAms()).thenReturn(mMockAms);
+ when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager);
+ when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
+ when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
+ initMockWindowManager();
- doAnswer(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
- Object[] args = invocationOnMock.getArguments();
- Region regionArg = (Region) args[0];
- regionArg.set(INITIAL_MAGNIFICATION_REGION);
- return null;
- }
- }).when(mMockWindowManager).getMagnificationRegion((Region) anyObject());
-
- ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
- verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
- mTargetAnimationListener = listenerArgumentCaptor.getValue();
- Mockito.reset(mMockValueAnimator); // Ignore other initialization
+ mMagnificationController = new MagnificationController(mMockControllerCtx, new Object());
}
@After
@@ -133,88 +119,132 @@
@Test
public void testRegister_WindowManagerAndContextRegisterListeners() {
- mMagnificationController.register();
+ register(DISPLAY_0);
+ register(DISPLAY_1);
+ register(INVALID_DISPLAY);
verify(mMockContext).registerReceiver(
(BroadcastReceiver) anyObject(), (IntentFilter) anyObject());
- verify(mMockWindowManager).setMagnificationCallbacks((MagnificationCallbacks) anyObject());
- assertTrue(mMagnificationController.isRegisteredLocked());
+ verify(mMockWindowManager).setMagnificationCallbacks(
+ eq(DISPLAY_0), (MagnificationCallbacks) anyObject());
+ verify(mMockWindowManager).setMagnificationCallbacks(
+ eq(DISPLAY_1), (MagnificationCallbacks) anyObject());
+ verify(mMockWindowManager).setMagnificationCallbacks(
+ eq(INVALID_DISPLAY), (MagnificationCallbacks) anyObject());
+ assertTrue(mMagnificationController.isRegistered(DISPLAY_0));
+ assertTrue(mMagnificationController.isRegistered(DISPLAY_1));
+ assertFalse(mMagnificationController.isRegistered(INVALID_DISPLAY));
}
@Test
public void testRegister_WindowManagerAndContextUnregisterListeners() {
- mMagnificationController.register();
- mMagnificationController.unregister();
-
+ register(DISPLAY_0);
+ register(DISPLAY_1);
+ mMagnificationController.unregister(DISPLAY_0);
+ verify(mMockContext, times(0)).unregisterReceiver((BroadcastReceiver) anyObject());
+ mMagnificationController.unregister(DISPLAY_1);
verify(mMockContext).unregisterReceiver((BroadcastReceiver) anyObject());
- verify(mMockWindowManager).setMagnificationCallbacks(null);
- assertFalse(mMagnificationController.isRegisteredLocked());
+ verify(mMockWindowManager).setMagnificationCallbacks(eq(DISPLAY_0), eq(null));
+ verify(mMockWindowManager).setMagnificationCallbacks(eq(DISPLAY_1), eq(null));
+ assertFalse(mMagnificationController.isRegistered(DISPLAY_0));
+ assertFalse(mMagnificationController.isRegistered(DISPLAY_1));
}
@Test
public void testInitialState_noMagnificationAndMagnificationRegionReadFromWindowManager() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ initialState_noMagnificationAndMagnificationRegionReadFromWindowManager(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void initialState_noMagnificationAndMagnificationRegionReadFromWindowManager(
+ int displayId) {
+ register(displayId);
MagnificationSpec expectedInitialSpec = getMagnificationSpec(1.0f, 0.0f, 0.0f);
Region initialMagRegion = new Region();
Rect initialBounds = new Rect();
- assertEquals(expectedInitialSpec, getCurrentMagnificationSpec());
- mMagnificationController.getMagnificationRegion(initialMagRegion);
- mMagnificationController.getMagnificationBounds(initialBounds);
+ assertEquals(expectedInitialSpec, getCurrentMagnificationSpec(displayId));
+ mMagnificationController.getMagnificationRegion(displayId, initialMagRegion);
+ mMagnificationController.getMagnificationBounds(displayId, initialBounds);
assertEquals(INITIAL_MAGNIFICATION_REGION, initialMagRegion);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS, initialBounds);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerX(),
- mMagnificationController.getCenterX(), 0.0f);
+ mMagnificationController.getCenterX(displayId), 0.0f);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerY(),
- mMagnificationController.getCenterY(), 0.0f);
+ mMagnificationController.getCenterY(displayId), 0.0f);
}
@Test
public void testNotRegistered_publicMethodsShouldBeBenign() {
- assertFalse(mMagnificationController.isMagnifying());
- assertFalse(mMagnificationController.magnificationRegionContains(100, 100));
- assertFalse(mMagnificationController.reset(true));
- assertFalse(mMagnificationController.setScale(2, 100, 100, true, 0));
- assertFalse(mMagnificationController.setCenter(100, 100, false, 1));
- assertFalse(mMagnificationController.setScaleAndCenter(1.5f, 100, 100, false, 2));
- assertTrue(mMagnificationController.getIdOfLastServiceToMagnify() < 0);
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ notRegistered_publicMethodsShouldBeBenign(i);
+ resetMockWindowManager();
+ }
+ }
- mMagnificationController.getMagnificationRegion(new Region());
- mMagnificationController.getMagnificationBounds(new Rect());
- mMagnificationController.getScale();
- mMagnificationController.getOffsetX();
- mMagnificationController.getOffsetY();
- mMagnificationController.getCenterX();
- mMagnificationController.getCenterY();
- mMagnificationController.offsetMagnifiedRegion(50, 50, 1);
- mMagnificationController.unregister();
+ private void notRegistered_publicMethodsShouldBeBenign(int displayId) {
+ assertFalse(mMagnificationController.isMagnifying(displayId));
+ assertFalse(mMagnificationController.magnificationRegionContains(displayId, 100, 100));
+ assertFalse(mMagnificationController.reset(displayId, true));
+ assertFalse(mMagnificationController.setScale(displayId, 2, 100, 100, true, 0));
+ assertFalse(mMagnificationController.setCenter(displayId, 100, 100, false, 1));
+ assertFalse(mMagnificationController.setScaleAndCenter(displayId,
+ 1.5f, 100, 100, false, 2));
+ assertTrue(mMagnificationController.getIdOfLastServiceToMagnify(displayId) < 0);
+
+ mMagnificationController.getMagnificationRegion(displayId, new Region());
+ mMagnificationController.getMagnificationBounds(displayId, new Rect());
+ mMagnificationController.getScale(displayId);
+ mMagnificationController.getOffsetX(displayId);
+ mMagnificationController.getOffsetY(displayId);
+ mMagnificationController.getCenterX(displayId);
+ mMagnificationController.getCenterY(displayId);
+ mMagnificationController.offsetMagnifiedRegion(displayId, 50, 50, 1);
+ mMagnificationController.unregister(displayId);
}
@Test
public void testSetScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState(int displayId) {
+ register(displayId);
final float scale = 2.0f;
final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
final PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale);
assertTrue(mMagnificationController
- .setScale(scale, center.x, center.y, false, SERVICE_ID_1));
+ .setScale(displayId, scale, center.x, center.y, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
final MagnificationSpec expectedSpec = getMagnificationSpec(scale, offsets);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
- assertThat(getCurrentMagnificationSpec(), closeTo(expectedSpec));
- assertEquals(center.x, mMagnificationController.getCenterX(), 0.0);
- assertEquals(center.y, mMagnificationController.getCenterY(), 0.0);
+ verify(mMockWindowManager).setMagnificationSpec(
+ eq(displayId), argThat(closeTo(expectedSpec)));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedSpec));
+ assertEquals(center.x, mMagnificationController.getCenterX(displayId), 0.0);
+ assertEquals(center.y, mMagnificationController.getCenterY(displayId), 0.0);
verify(mMockValueAnimator, times(0)).start();
}
@Test
public void testSetScale_withPivotAndAnimation_stateChangesAndAnimationHappens() {
- mMagnificationController.register();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setScale_withPivotAndAnimation_stateChangesAndAnimationHappens(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setScale_withPivotAndAnimation_stateChangesAndAnimationHappens(int displayId) {
+ register(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
float scale = 2.0f;
PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mMagnificationController
- .setScale(scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
+ .setScale(displayId, scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
// New center should be halfway between original center and pivot
@@ -223,467 +253,645 @@
PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
- assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec));
verify(mMockValueAnimator).start();
// Initial value
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(startSpec);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec));
// Intermediate point
Mockito.reset(mMockWindowManager);
float fraction = 0.5f;
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
// Final value
Mockito.reset(mMockWindowManager);
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
}
@Test
public void testSetCenter_whileMagnifying_noAnimation_centerMoves() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setCenter_whileMagnifying_noAnimation_centerMoves(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setCenter_whileMagnifying_noAnimation_centerMoves(int displayId) {
+ register(displayId);
// First zoom in
float scale = 2.0f;
- assertTrue(mMagnificationController.setScale(scale,
+ assertTrue(mMagnificationController.setScale(displayId, scale,
INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY(),
false, SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mMagnificationController
- .setCenter(newCenter.x, newCenter.y, false, SERVICE_ID_1));
+ .setCenter(displayId, newCenter.x, newCenter.y, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
MagnificationSpec expectedSpec = getMagnificationSpec(scale, expectedOffsets);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
+ verify(mMockWindowManager).setMagnificationSpec(
+ eq(displayId), argThat(closeTo(expectedSpec)));
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.0);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.0);
verify(mMockValueAnimator, times(0)).start();
}
@Test
public void testSetScaleAndCenter_animated_stateChangesAndAnimationHappens() {
- mMagnificationController.register();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setScaleAndCenter_animated_stateChangesAndAnimationHappens(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setScaleAndCenter_animated_stateChangesAndAnimationHappens(int displayId) {
+ register(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
float scale = 2.5f;
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
- assertTrue(mMagnificationController.setScaleAndCenter(scale, newCenter.x, newCenter.y,
- true, SERVICE_ID_1));
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId, scale, newCenter.x,
+ newCenter.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
- assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
- verify(mMockAms).notifyMagnificationChanged(
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec));
+ verify(mMockAms).notifyMagnificationChanged(displayId,
INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
verify(mMockValueAnimator).start();
// Initial value
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(startSpec);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec));
// Intermediate point
Mockito.reset(mMockWindowManager);
float fraction = 0.33f;
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
// Final value
Mockito.reset(mMockWindowManager);
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
}
@Test
public void testSetScaleAndCenter_scaleOutOfBounds_cappedAtLimits() {
- mMagnificationController.register();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setScaleAndCenter_scaleOutOfBounds_cappedAtLimits(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setScaleAndCenter_scaleOutOfBounds_cappedAtLimits(int displayId) {
+ register(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter,
MagnificationController.MAX_SCALE);
MagnificationSpec endSpec = getMagnificationSpec(
MagnificationController.MAX_SCALE, offsets);
- assertTrue(mMagnificationController.setScaleAndCenter(
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId,
MagnificationController.MAX_SCALE + 1.0f,
newCenter.x, newCenter.y, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
Mockito.reset(mMockWindowManager);
// Verify that we can't zoom below 1x
- assertTrue(mMagnificationController.setScaleAndCenter(0.5f,
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId, 0.5f,
INITIAL_MAGNIFICATION_BOUNDS_CENTER.x, INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.x,
- mMagnificationController.getCenterX(), 0.5);
+ mMagnificationController.getCenterX(displayId), 0.5);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
- mMagnificationController.getCenterY(), 0.5);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
+ mMagnificationController.getCenterY(displayId), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(startSpec)));
}
@Test
public void testSetScaleAndCenter_centerOutOfBounds_cappedAtLimits() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setScaleAndCenter_centerOutOfBounds_cappedAtLimits(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setScaleAndCenter_centerOutOfBounds_cappedAtLimits(int displayId) {
+ register(displayId);
float scale = 2.0f;
// Off the edge to the top and left
- assertTrue(mMagnificationController.setScaleAndCenter(
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId,
scale, -100f, -200f, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
- verify(mMockWindowManager).setMagnificationSpec(
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
Mockito.reset(mMockWindowManager);
// Off the edge to the bottom and right
- assertTrue(mMagnificationController.setScaleAndCenter(scale,
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId, scale,
INITIAL_MAGNIFICATION_BOUNDS.right + 1, INITIAL_MAGNIFICATION_BOUNDS.bottom + 1,
false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
- verify(mMockWindowManager).setMagnificationSpec(
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
}
@Test
public void testMagnificationRegionChanged_serviceNotified() {
- mMagnificationController.register();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ magnificationRegionChanged_serviceNotified(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void magnificationRegionChanged_serviceNotified(int displayId) {
+ register(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
callbacks.onMagnificationRegionChanged(OTHER_REGION);
mMessageCapturingHandler.sendAllMessages();
- verify(mMockAms).notifyMagnificationChanged(OTHER_REGION, 1.0f,
+ verify(mMockAms).notifyMagnificationChanged(displayId, OTHER_REGION, 1.0f,
OTHER_MAGNIFICATION_BOUNDS.centerX(), OTHER_MAGNIFICATION_BOUNDS.centerY());
}
@Test
public void testOffsetMagnifiedRegion_whileMagnifying_offsetsMove() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ offsetMagnifiedRegion_whileMagnifying_offsetsMove(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void offsetMagnifiedRegion_whileMagnifying_offsetsMove(int displayId) {
+ register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
// First zoom in
assertTrue(mMagnificationController
- .setScaleAndCenter(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1));
+ .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
Mockito.reset(mMockWindowManager);
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
- mMagnificationController.offsetMagnifiedRegion(
- startOffsets.x - newOffsets.x, startOffsets.y - newOffsets.y, SERVICE_ID_1);
+ mMagnificationController.offsetMagnifiedRegion(displayId,
+ startOffsets.x - newOffsets.x, startOffsets.y - newOffsets.y,
+ SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec expectedSpec = getMagnificationSpec(scale, newOffsets);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(expectedSpec)));
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.0);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.0);
verify(mMockValueAnimator, times(0)).start();
}
@Test
public void testOffsetMagnifiedRegion_whileNotMagnifying_hasNoEffect() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ offsetMagnifiedRegion_whileNotMagnifying_hasNoEffect(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void offsetMagnifiedRegion_whileNotMagnifying_hasNoEffect(int displayId) {
+ register(displayId);
Mockito.reset(mMockWindowManager);
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
- mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ mMagnificationController.offsetMagnifiedRegion(displayId, 10, 10, SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
+ mMagnificationController.offsetMagnifiedRegion(displayId, -10, -10, SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testOffsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ offsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void offsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect(int displayId) {
+ register(displayId);
float scale = 2.0f;
// Upper left edges
PointF ulCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
assertTrue(mMagnificationController
- .setScaleAndCenter(scale, ulCenter.x, ulCenter.y, false, SERVICE_ID_1));
+ .setScaleAndCenter(displayId, scale, ulCenter.x, ulCenter.y, false,
+ SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
- MagnificationSpec ulSpec = getCurrentMagnificationSpec();
- mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
- assertThat(getCurrentMagnificationSpec(), closeTo(ulSpec));
+ MagnificationSpec ulSpec = getCurrentMagnificationSpec(displayId);
+ mMagnificationController.offsetMagnifiedRegion(displayId, -10, -10,
+ SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(ulSpec));
verifyNoMoreInteractions(mMockWindowManager);
// Lower right edges
PointF lrCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mMagnificationController
- .setScaleAndCenter(scale, lrCenter.x, lrCenter.y, false, SERVICE_ID_1));
+ .setScaleAndCenter(displayId, scale, lrCenter.x, lrCenter.y, false,
+ SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
- MagnificationSpec lrSpec = getCurrentMagnificationSpec();
- mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
- assertThat(getCurrentMagnificationSpec(), closeTo(lrSpec));
+ MagnificationSpec lrSpec = getCurrentMagnificationSpec(displayId);
+ mMagnificationController.offsetMagnifiedRegion(displayId, 10, 10,
+ SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(lrSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testGetIdOfLastServiceToChange_returnsCorrectValue() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ getIdOfLastServiceToChange_returnsCorrectValue(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void getIdOfLastServiceToChange_returnsCorrectValue(int displayId) {
+ register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
assertTrue(mMagnificationController
- .setScale(2.0f, startCenter.x, startCenter.y, false, SERVICE_ID_1));
- assertEquals(SERVICE_ID_1, mMagnificationController.getIdOfLastServiceToMagnify());
+ .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1));
+ assertEquals(SERVICE_ID_1, mMagnificationController.getIdOfLastServiceToMagnify(displayId));
assertTrue(mMagnificationController
- .setScale(1.5f, startCenter.x, startCenter.y, false, SERVICE_ID_2));
- assertEquals(SERVICE_ID_2, mMagnificationController.getIdOfLastServiceToMagnify());
+ .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
+ SERVICE_ID_2));
+ assertEquals(SERVICE_ID_2, mMagnificationController.getIdOfLastServiceToMagnify(displayId));
}
@Test
public void testResetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ resetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void resetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled(int displayId) {
+ register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
mMagnificationController
- .setScale(2.0f, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+ .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1);
mMagnificationController
- .setScale(1.5f, startCenter.x, startCenter.y, false, SERVICE_ID_2);
- assertFalse(mMagnificationController.resetIfNeeded(SERVICE_ID_1));
- assertTrue(mMagnificationController.isMagnifying());
- assertTrue(mMagnificationController.resetIfNeeded(SERVICE_ID_2));
- assertFalse(mMagnificationController.isMagnifying());
+ .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
+ SERVICE_ID_2);
+ assertFalse(mMagnificationController.resetIfNeeded(displayId, SERVICE_ID_1));
+ assertTrue(mMagnificationController.isMagnifying(displayId));
+ assertTrue(mMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2));
+ assertFalse(mMagnificationController.isMagnifying(displayId));
}
@Test
public void testSetUserId_resetsOnlyIfIdChanges() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ testSetUserId_resetsOnlyIfIdChanges(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void testSetUserId_resetsOnlyIfIdChanges(int displayId) {
final int userId1 = 1;
final int userId2 = 2;
- mMagnificationController.register();
+ register(displayId);
mMagnificationController.setUserId(userId1);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+ mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1);
mMagnificationController.setUserId(userId1);
- assertTrue(mMagnificationController.isMagnifying());
+ assertTrue(mMagnificationController.isMagnifying(displayId));
mMagnificationController.setUserId(userId2);
- assertFalse(mMagnificationController.isMagnifying());
+ assertFalse(mMagnificationController.isMagnifying(displayId));
}
@Test
public void testResetIfNeeded_doesWhatItSays() {
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ testResetIfNeeded_doesWhatItSays(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void testResetIfNeeded_doesWhatItSays(int displayId) {
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
reset(mMockAms);
- assertTrue(mMagnificationController.resetIfNeeded(false));
- verify(mMockAms).notifyMagnificationChanged(
+ assertTrue(mMagnificationController.resetIfNeeded(displayId, false));
+ verify(mMockAms).notifyMagnificationChanged(eq(displayId),
eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyFloat(), anyFloat());
- assertFalse(mMagnificationController.isMagnifying());
- assertFalse(mMagnificationController.resetIfNeeded(false));
+ assertFalse(mMagnificationController.isMagnifying(displayId));
+ assertFalse(mMagnificationController.resetIfNeeded(displayId, false));
}
@Test
public void testTurnScreenOff_resetsMagnification() {
- mMagnificationController.register();
+ register(DISPLAY_0);
+ register(DISPLAY_1);
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mMockContext).registerReceiver(
broadcastReceiverCaptor.capture(), (IntentFilter) anyObject());
BroadcastReceiver br = broadcastReceiverCaptor.getValue();
- zoomIn2xToMiddle();
+ zoomIn2xToMiddle(DISPLAY_0);
+ zoomIn2xToMiddle(DISPLAY_1);
mMessageCapturingHandler.sendAllMessages();
br.onReceive(mMockContext, null);
mMessageCapturingHandler.sendAllMessages();
- assertFalse(mMagnificationController.isMagnifying());
+ assertFalse(mMagnificationController.isMagnifying(DISPLAY_0));
+ assertFalse(mMagnificationController.isMagnifying(DISPLAY_1));
}
@Test
public void testUserContextChange_resetsMagnification() {
- mMagnificationController.register();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ contextChange_resetsMagnification(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void contextChange_resetsMagnification(int displayId) {
+ register(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
callbacks.onUserContextChanged();
mMessageCapturingHandler.sendAllMessages();
- assertFalse(mMagnificationController.isMagnifying());
+ assertFalse(mMagnificationController.isMagnifying(displayId));
}
@Test
public void testRotation_resetsMagnification() {
- mMagnificationController.register();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ rotation_resetsMagnification(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void rotation_resetsMagnification(int displayId) {
+ register(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- assertTrue(mMagnificationController.isMagnifying());
+ assertTrue(mMagnificationController.isMagnifying(displayId));
callbacks.onRotationChanged(0);
mMessageCapturingHandler.sendAllMessages();
- assertFalse(mMagnificationController.isMagnifying());
+ assertFalse(mMagnificationController.isMagnifying(displayId));
}
@Test
public void testBoundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ boundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void boundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange(int displayId) {
// Going from a small region to a large one leads to no issues
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
callbacks.onMagnificationRegionChanged(OTHER_REGION_COMPAT);
mMessageCapturingHandler.sendAllMessages();
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testBoundsChange_whileZoomingWithCompatibleSpec_noSpecChange() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ boundsChange_whileZoomingWithCompatibleSpec_noSpecChange(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void boundsChange_whileZoomingWithCompatibleSpec_noSpecChange(int displayId) {
+ register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
// setting animate parameter to true is differ from zoomIn2xToMiddle()
- mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, true,
+ SERVICE_ID_1);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
callbacks.onMagnificationRegionChanged(OTHER_REGION_COMPAT);
mMessageCapturingHandler.sendAllMessages();
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testBoundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ boundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void boundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained(
+ int displayId) {
// In a large region, pan to the farthest point possible
- mMagnificationController.register();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ register(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
callbacks.onMagnificationRegionChanged(OTHER_REGION);
mMessageCapturingHandler.sendAllMessages();
PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+ mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(startSpec)));
Mockito.reset(mMockWindowManager);
callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec endSpec = getCurrentMagnificationSpec();
+ MagnificationSpec endSpec = getCurrentMagnificationSpec(displayId);
assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
}
@Test
public void testBoundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec() {
- mMagnificationController.register();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ boundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void boundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec(
+ int displayId) {
+ register(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
callbacks.onMagnificationRegionChanged(OTHER_REGION);
mMessageCapturingHandler.sendAllMessages();
PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
+ mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, true,
+ SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
when(mMockValueAnimator.isRunning()).thenReturn(true);
callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
mMessageCapturingHandler.sendAllMessages();
verify(mMockValueAnimator).cancel();
- MagnificationSpec endSpec = getCurrentMagnificationSpec();
+ MagnificationSpec endSpec = getCurrentMagnificationSpec(displayId);
assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
}
@Test
public void testRequestRectOnScreen_rectAlreadyOnScreen_doesNothing() {
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ requestRectOnScreen_rectAlreadyOnScreen_doesNothing(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void requestRectOnScreen_rectAlreadyOnScreen_doesNothing(int displayId) {
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
int centerX = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.x;
int centerY = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.y;
callbacks.onRectangleOnScreenRequested(centerX - 1, centerY - 1, centerX + 1, centerY - 1);
mMessageCapturingHandler.sendAllMessages();
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testRequestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen() {
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ requestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void requestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen(int displayId) {
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
callbacks.onRectangleOnScreenRequested(0, 0, 1, 1);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, 0);
- assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(expectedEndSpec)));
}
@Test
public void testRequestRectOnScreen_garbageInput_doesNothing() {
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ requestRectOnScreen_garbageInput_doesNothing(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void requestRectOnScreen_garbageInput_doesNothing(int displayId) {
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
callbacks.onRectangleOnScreenRequested(0, 0, -50, -50);
mMessageCapturingHandler.sendAllMessages();
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testRequestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ requestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void requestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale(
+ int displayId) {
Locale.setDefault(new Locale("en", "us"));
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
Mockito.reset(mMockWindowManager);
Rect wideRect = new Rect(0, 50, 100, 51);
callbacks.onRectangleOnScreenRequested(
wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, startSpec.offsetY);
- assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(expectedEndSpec)));
Mockito.reset(mMockWindowManager);
// Repeat with RTL
@@ -692,50 +900,66 @@
wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
mMessageCapturingHandler.sendAllMessages();
expectedEndSpec = getMagnificationSpec(2.0f, -100, startSpec.offsetY);
- assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(expectedEndSpec)));
}
@Test
public void testRequestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen() {
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ requestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void requestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen(int displayId) {
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
Mockito.reset(mMockWindowManager);
Rect tallRect = new Rect(50, 0, 51, 100);
callbacks.onRectangleOnScreenRequested(
tallRect.left, tallRect.top, tallRect.right, tallRect.bottom);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, startSpec.offsetX, 0);
- assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(expectedEndSpec)));
}
@Test
public void testChangeMagnification_duringAnimation_animatesToNewValue() {
- mMagnificationController.register();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ changeMagnification_duringAnimation_animatesToNewValue(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void changeMagnification_duringAnimation_animatesToNewValue(int displayId) {
+ register(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
float scale = 2.5f;
PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
MagnificationSpec firstEndSpec = getMagnificationSpec(
scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, firstCenter, scale));
- assertTrue(mMagnificationController.setScaleAndCenter(scale, firstCenter.x, firstCenter.y,
- true, SERVICE_ID_1));
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId,
+ scale, firstCenter.x, firstCenter.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
- assertEquals(firstCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(firstCenter.y, mMagnificationController.getCenterY(), 0.5);
- assertThat(getCurrentMagnificationSpec(), closeTo(firstEndSpec));
+ assertEquals(firstCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(firstCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(firstEndSpec));
verify(mMockValueAnimator, times(1)).start();
// Initial value
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(startSpec);
- verify(mMockAms).notifyMagnificationChanged(
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec));
+ verify(mMockAms).notifyMagnificationChanged(displayId,
INITIAL_MAGNIFICATION_REGION, scale, firstCenter.x, firstCenter.y);
Mockito.reset(mMockWindowManager);
@@ -745,32 +969,34 @@
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
MagnificationSpec intermediateSpec1 =
getInterpolatedMagSpec(startSpec, firstEndSpec, fraction);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(intermediateSpec1)));
Mockito.reset(mMockWindowManager);
PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
MagnificationSpec newEndSpec = getMagnificationSpec(
scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale));
- assertTrue(mMagnificationController.setCenter(
+ assertTrue(mMagnificationController.setCenter(displayId,
newCenter.x, newCenter.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
// Animation should have been restarted
verify(mMockValueAnimator, times(2)).start();
- verify(mMockAms).notifyMagnificationChanged(
+ verify(mMockAms).notifyMagnificationChanged(displayId,
INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
// New starting point should be where we left off
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(intermediateSpec1)));
Mockito.reset(mMockWindowManager);
// Second intermediate point
fraction = 0.5f;
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getInterpolatedMagSpec(intermediateSpec1, newEndSpec, fraction))));
Mockito.reset(mMockWindowManager);
@@ -778,21 +1004,54 @@
Mockito.reset(mMockWindowManager);
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(newEndSpec)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(newEndSpec)));
}
- private void zoomIn2xToMiddle() {
+ private void initMockWindowManager() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ when(mMockWindowManager.setMagnificationCallbacks(eq(i), any())).thenReturn(true);
+ }
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Object[] args = invocationOnMock.getArguments();
+ Region regionArg = (Region) args[1];
+ regionArg.set(INITIAL_MAGNIFICATION_REGION);
+ return null;
+ }
+ }).when(mMockWindowManager).getMagnificationRegion(anyInt(), (Region) anyObject());
+ }
+
+ private void resetMockWindowManager() {
+ Mockito.reset(mMockWindowManager);
+ initMockWindowManager();
+ }
+
+ private void register(int displayId) {
+ mMockValueAnimator = mock(ValueAnimator.class);
+ when(mMockControllerCtx.newValueAnimator()).thenReturn(mMockValueAnimator);
+ mMagnificationController.register(displayId);
+ ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
+ verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
+ mTargetAnimationListener = listenerArgumentCaptor.getValue();
+ Mockito.reset(mMockValueAnimator); // Ignore other initialization
+ }
+
+ private void zoomIn2xToMiddle(int displayId) {
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
- assertTrue(mMagnificationController.isMagnifying());
+ mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1);
+ assertTrue(mMagnificationController.isMagnifying(displayId));
}
- private MagnificationCallbacks getMagnificationCallbacks() {
+ private MagnificationCallbacks getMagnificationCallbacks(int displayId) {
ArgumentCaptor<MagnificationCallbacks> magnificationCallbacksCaptor =
ArgumentCaptor.forClass(MagnificationCallbacks.class);
verify(mMockWindowManager)
- .setMagnificationCallbacks(magnificationCallbacksCaptor.capture());
+ .setMagnificationCallbacks(eq(displayId), magnificationCallbacksCaptor.capture());
return magnificationCallbacksCaptor.getValue();
}
@@ -823,9 +1082,10 @@
return spec;
}
- private MagnificationSpec getCurrentMagnificationSpec() {
- return getMagnificationSpec(mMagnificationController.getScale(),
- mMagnificationController.getOffsetX(), mMagnificationController.getOffsetY());
+ private MagnificationSpec getCurrentMagnificationSpec(int displayId) {
+ return getMagnificationSpec(mMagnificationController.getScale(displayId),
+ mMagnificationController.getOffsetX(displayId),
+ mMagnificationController.getOffsetY(displayId));
}
private MagSpecMatcher closeTo(MagnificationSpec spec) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
index 032074a..d91ce39 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
@@ -26,16 +26,19 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
+import android.os.Handler;
import android.os.Message;
import android.util.DebugUtils;
import android.view.InputDevice;
@@ -104,14 +107,9 @@
public static final float DEFAULT_X = 301;
public static final float DEFAULT_Y = 299;
+ private static final int DISPLAY_0 = 0;
+
private Context mContext;
- final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
- final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
- final MessageCapturingHandler mMessageCapturingHandler =
- new MessageCapturingHandler(null);
- final ValueAnimator mMockValueAnimator = mock(ValueAnimator.class);
- MagnificationController.SettingsBridge mMockSettingsBridge =
- mock(MagnificationController.SettingsBridge.class);
MagnificationController mMagnificationController;
private OffsettableClock mClock;
@@ -123,18 +121,26 @@
@Before
public void setUp() {
mContext = InstrumentationRegistry.getContext();
- mMagnificationController = new MagnificationController(mContext, mMockAms, new Object(),
- mMessageCapturingHandler, mMockWindowManager, mMockValueAnimator,
- mMockSettingsBridge) {
+ final MagnificationController.ControllerContext mockController =
+ mock(MagnificationController.ControllerContext.class);
+ final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
+ when(mockController.getContext()).thenReturn(mContext);
+ when(mockController.getAms()).thenReturn(mock(AccessibilityManagerService.class));
+ when(mockController.getWindowManager()).thenReturn(mockWindowManager);
+ when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper()));
+ when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
+ when(mockController.getAnimationDuration()).thenReturn(1000L);
+ when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true);
+ mMagnificationController = new MagnificationController(mockController, new Object()) {
@Override
- public boolean magnificationRegionContains(float x, float y) {
+ public boolean magnificationRegionContains(int displayId, float x, float y) {
return true;
}
@Override
- void setForceShowMagnifiableBounds(boolean show) {}
+ void setForceShowMagnifiableBounds(int displayId, boolean show) {}
};
- mMagnificationController.register();
+ mMagnificationController.register(DISPLAY_0);
mClock = new OffsettableClock.Stopped();
boolean detectTripleTap = true;
@@ -144,7 +150,7 @@
@After
public void tearDown() {
- mMagnificationController.unregister();
+ mMagnificationController.unregister(DISPLAY_0);
}
@NonNull
@@ -302,6 +308,24 @@
assertZoomsImmediatelyOnSwipeFrom(STATE_SHORTCUT_TRIGGERED);
}
+ @Test
+ public void testMultiTap_outOfDistanceSlop_shouldInIdle() {
+ // All delay motion events should be sent, if multi-tap with out of distance slop.
+ // STATE_IDLE will check if tapCount() < 2.
+ allowEventDelegation();
+ assertStaysIn(STATE_IDLE, () -> {
+ tap();
+ tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+ });
+ assertStaysIn(STATE_IDLE, () -> {
+ tap();
+ tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+ tap();
+ tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+ tap();
+ });
+ }
+
private void assertZoomsImmediatelyOnSwipeFrom(int state) {
goFromStateIdleTo(state);
swipeAndHold();
@@ -509,7 +533,7 @@
}
private boolean isZoomed() {
- return mMgh.mMagnificationController.isMagnifying();
+ return mMgh.mMagnificationController.isMagnifying(DISPLAY_0);
}
private int tapCount() {
@@ -525,6 +549,11 @@
send(upEvent());
}
+ private void tap(float x, float y) {
+ send(downEvent(x, y));
+ send(upEvent(x, y));
+ }
+
private void swipe() {
swipeAndHold();
send(upEvent());
@@ -566,18 +595,26 @@
}
private MotionEvent downEvent() {
+ return downEvent(DEFAULT_X, DEFAULT_Y);
+ }
+
+ private MotionEvent downEvent(float x, float y) {
mLastDownTime = mClock.now();
return fromTouchscreen(MotionEvent.obtain(mLastDownTime, mLastDownTime,
- ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0));
+ ACTION_DOWN, x, y, 0));
}
private MotionEvent upEvent() {
- return upEvent(mLastDownTime);
+ return upEvent(DEFAULT_X, DEFAULT_Y, mLastDownTime);
}
- private MotionEvent upEvent(long downTime) {
+ private MotionEvent upEvent(float x, float y) {
+ return upEvent(x, y, mLastDownTime);
+ }
+
+ private MotionEvent upEvent(float x, float y, long downTime) {
return fromTouchscreen(MotionEvent.obtain(downTime, mClock.now(),
- MotionEvent.ACTION_UP, DEFAULT_X, DEFAULT_Y, 0));
+ MotionEvent.ACTION_UP, x, y, 0));
}
private MotionEvent pointerEvent(int action, float x, float y) {
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
new file mode 100644
index 0000000..65af677
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.adb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.FgThread;
+
+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.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public final class AdbDebuggingManagerTest {
+
+ private static final String TAG = "AdbDebuggingManagerTest";
+
+ // This component is passed to the AdbDebuggingManager to act as the activity that can confirm
+ // unknown adb keys. An overlay package was first attempted to override the
+ // config_customAdbPublicKeyConfirmationComponent config, but the value from that package was
+ // not being read.
+ private static final String ADB_CONFIRM_COMPONENT =
+ "com.android.frameworks.servicestests/"
+ + "com.android.server.adb.AdbDebuggingManagerTestActivity";
+
+ // The base64 encoding of the values 'test key 1' and 'test key 2'.
+ private static final String TEST_KEY_1 = "dGVzdCBrZXkgMQo=";
+ private static final String TEST_KEY_2 = "dGVzdCBrZXkgMgo=";
+
+ // This response is received from the AdbDebuggingHandler when the key is allowed to connect
+ private static final String RESPONSE_KEY_ALLOWED = "OK";
+ // This response is received from the AdbDebuggingHandler when the key is not allowed to connect
+ private static final String RESPONSE_KEY_DENIED = "NO";
+
+ // wait up to 5 seconds for any blocking queries
+ private static final long TIMEOUT = 5000;
+ private static final TimeUnit TIMEOUT_TIME_UNIT = TimeUnit.MILLISECONDS;
+
+ private Context mContext;
+ private AdbDebuggingManager mManager;
+ private AdbDebuggingManager.AdbDebuggingThread mThread;
+ private AdbDebuggingManager.AdbDebuggingHandler mHandler;
+ private AdbDebuggingManager.AdbKeyStore mKeyStore;
+ private BlockingQueue<TestResult> mBlockingQueue;
+ private long mOriginalAllowedConnectionTime;
+ private File mKeyFile;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ mManager = new AdbDebuggingManager(mContext, ADB_CONFIRM_COMPONENT);
+ mKeyFile = new File(mContext.getFilesDir(), "test_adb_keys.xml");
+ if (mKeyFile.exists()) {
+ mKeyFile.delete();
+ }
+ mThread = new AdbDebuggingThreadTest();
+ mKeyStore = mManager.new AdbKeyStore(mKeyFile);
+ mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper(), mThread, mKeyStore);
+ mOriginalAllowedConnectionTime = mKeyStore.getAllowedConnectionTime();
+ mBlockingQueue = new ArrayBlockingQueue<>(1);
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mKeyStore.deleteKeyStore();
+ setAllowedConnectionTime(mOriginalAllowedConnectionTime);
+ }
+
+ /**
+ * Sets the allowed connection time within which a subsequent connection from a key for which
+ * the user selected the 'Always allow' option will be allowed without user interaction.
+ */
+ private void setAllowedConnectionTime(long connectionTime) {
+ Settings.Global.putLong(mContext.getContentResolver(),
+ Settings.Global.ADB_ALLOWED_CONNECTION_TIME, connectionTime);
+ };
+
+ @Test
+ public void testAllowNewKeyOnce() throws Exception {
+ // Verifies the behavior when a new key first attempts to connect to a device. During the
+ // first connection the ADB confirmation activity should be launched to prompt the user to
+ // allow the connection with an option to always allow connections from this key.
+
+ // Verify if the user allows the key but does not select the option to 'always
+ // allow' that the connection is allowed but the key is not stored.
+ runAdbTest(TEST_KEY_1, true, false, false);
+ }
+
+ @Test
+ public void testDenyNewKey() throws Exception {
+ // Verifies if the user does not allow the key then the connection is not allowed and the
+ // key is not stored.
+ runAdbTest(TEST_KEY_1, false, false, false);
+ }
+
+ @Test
+ public void testDisconnectAlwaysAllowKey() throws Exception {
+ // When a key is disconnected from a device ADB should send a disconnect message; this
+ // message should trigger an update of the last connection time for the currently connected
+ // key.
+
+ // Allow a connection from a new key with the 'Always allow' option selected.
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Get the last connection time for the currently connected key to verify that it is updated
+ // after the disconnect.
+ long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+ // Sleep for a small amount of time to ensure a difference can be observed in the last
+ // connection time after a disconnect.
+ Thread.sleep(10);
+
+ // Send the disconnect message for the currently connected key to trigger an update of the
+ // last connection time.
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT).sendToTarget();
+
+ // Use a latch to ensure the test does not exit untill the Runnable has been processed.
+ CountDownLatch latch = new CountDownLatch(1);
+
+ // Post a new Runnable to the handler to ensure it runs after the disconnect message is
+ // processed.
+ mHandler.post(() -> {
+ assertNotEquals(
+ "The last connection time was not updated after the disconnect",
+ lastConnectionTime,
+ mKeyStore.getLastConnectionTime(TEST_KEY_1));
+ latch.countDown();
+ });
+ if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+ fail("The Runnable to verify the last connection time was updated did not complete "
+ + "within the timeout period");
+ }
+ }
+
+ @Test
+ public void testDisconnectAllowedOnceKey() throws Exception {
+ // When a key is disconnected ADB should send a disconnect message; this message should
+ // essentially result in a noop for keys that the user only allows once since the last
+ // connection time is not maintained for these keys.
+
+ // Allow a connection from a new key with the 'Always allow' option set to false
+ runAdbTest(TEST_KEY_1, true, false, false);
+
+ // Send the disconnect message for the currently connected key.
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT).sendToTarget();
+
+ // Verify that the disconnected key is not automatically allowed on a subsequent connection.
+ runAdbTest(TEST_KEY_1, true, false, false);
+ }
+
+ @Test
+ public void testAlwaysAllowConnectionFromKey() throws Exception {
+ // Verifies when the user selects the 'Always allow' option for the current key that
+ // subsequent connection attempts from that key are allowed.
+
+ // Allow a connection from a new key with the 'Always allow' option selected.
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Next attempt another connection with the same key and verify that the activity to prompt
+ // the user to accept the key is not launched.
+ runAdbTest(TEST_KEY_1, true, true, true);
+
+ // Verify that a different key is not automatically allowed.
+ runAdbTest(TEST_KEY_2, false, false, false);
+ }
+
+ @Test
+ public void testOriginalAlwaysAllowBehavior() throws Exception {
+ // If the Settings.Global.ADB_ALLOWED_CONNECTION_TIME setting is set to 0 then the original
+ // behavior of 'Always allow' should be restored.
+
+ // Accept the test key with the 'Always allow' option selected.
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Set the connection time to 0 to restore the original behavior.
+ setAllowedConnectionTime(0);
+
+ // Set the last connection time to the test key to a very small value to ensure it would
+ // fail the new test but would be allowed with the original behavior.
+ mKeyStore.setLastConnectionTime(TEST_KEY_1, 1);
+
+ // Run the test with the key and verify that the connection is automatically allowed.
+ runAdbTest(TEST_KEY_1, true, true, true);
+ }
+
+ @Test
+ public void testLastConnectionTimeUpdatedByScheduledJob() throws Exception {
+ // If a development device is left connected to a system beyond the allowed connection time
+ // a reboot of the device while connected could make it appear as though the last connection
+ // time is beyond the allowed window. A scheduled job runs daily while a key is connected
+ // to update the last connection time to the current time; this ensures if the device is
+ // rebooted while connected to a system the last connection time should be within 24 hours.
+
+ // Allow the key to connect with the 'Always allow' option selected
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Get the current last connection time for comparison after the scheduled job is run
+ long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+ // Sleep a small amount of time to ensure that the updated connection time changes
+ Thread.sleep(10);
+
+ // Send a message to the handler to update the last connection time for the active key
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME)
+ .sendToTarget();
+
+ // Post a Runnable to the handler to ensure it runs after the update key connection time
+ // message is processed.
+ CountDownLatch latch = new CountDownLatch(1);
+ mHandler.post(() -> {
+ assertNotEquals(
+ "The last connection time of the key was not updated after the update key "
+ + "connection time message",
+ lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
+ latch.countDown();
+ });
+ if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+ fail("The Runnable to verify the last connection time was updated did not complete "
+ + "within the timeout period");
+ }
+ }
+
+ @Test
+ public void testKeystorePersisted() throws Exception {
+ // After any updates are made to the key store a message should be sent to persist the
+ // key store. This test verifies that a key that is always allowed is persisted in the key
+ // store along with its last connection time.
+
+ // Allow the key to connect with the 'Always allow' option selected
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Send a message to the handler to persist the updated keystore.
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEY_STORE)
+ .sendToTarget();
+
+ // Post a Runnable to the handler to ensure the persist key store message has been processed
+ // using a new AdbKeyStore backed by the key file.
+ mHandler.post(() -> assertTrue(
+ "The key with the 'Always allow' option selected was not persisted in the keystore",
+ mManager.new AdbKeyStore(mKeyFile).isKeyAuthorized(TEST_KEY_1)));
+
+ // Get the current last connection time to ensure it is updated in the persisted keystore.
+ long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+ // Sleep a small amount of time to ensure the last connection time is updated.
+ Thread.sleep(10);
+
+ // Send a message to the handler to update the last connection time for the active key.
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME)
+ .sendToTarget();
+
+ // Persist the updated last connection time.
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEY_STORE)
+ .sendToTarget();
+
+ // Post a Runnable with a new key store backed by the key file to verify that the last
+ // connection time obtained above is different from the persisted updated value.
+ CountDownLatch latch = new CountDownLatch(1);
+ mHandler.post(() -> {
+ assertNotEquals(
+ "The last connection time in the key file was not updated after the update "
+ + "connection time message", lastConnectionTime,
+ mManager.new AdbKeyStore(mKeyFile).getLastConnectionTime(TEST_KEY_1));
+ latch.countDown();
+ });
+ if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+ fail("The Runnable to verify the last connection time was updated did not complete "
+ + "within the timeout period");
+ }
+ }
+
+ @Test
+ public void testAdbClearRemovesActiveKey() throws Exception {
+ // If the user selects the option to 'Revoke USB debugging authorizations' while an 'Always
+ // allow' key is connected that key should be deleted as well.
+
+ // Allow the key to connect with the 'Always allow' option selected
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Send a message to the handler to clear the adb authorizations.
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CLEAR).sendToTarget();
+
+ // Send a message to disconnect the currently connected key
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT).sendToTarget();
+
+ // Post a Runnable to ensure the disconnect has completed to verify the 'Always allow' key
+ // that was connected when the clear was sent requires authorization.
+ CountDownLatch latch = new CountDownLatch(1);
+ mHandler.post(() -> {
+ assertFalse(
+ "The currently connected 'always allow' key should not be authorized after an"
+ + " adb"
+ + " clear message.",
+ mKeyStore.isKeyAuthorized(TEST_KEY_1));
+ latch.countDown();
+ });
+ if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+ fail("The Runnable to verify the key is not authorized did not complete within the "
+ + "timeout period");
+ }
+ }
+
+ @Test
+ public void testAdbGrantRevokedIfLastConnectionBeyondAllowedTime() throws Exception {
+ // If the user selects the 'Always allow' option then subsequent connections from the key
+ // will be allowed as long as the connection is within the allowed window. Once the last
+ // connection time is beyond this window the user should be prompted to allow the key again.
+
+ // Allow the key to connect with the 'Always allow' option selected
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Set the allowed window to a small value to ensure the time is beyond the allowed window.
+ setAllowedConnectionTime(1);
+
+ // Sleep for a small amount of time to exceed the allowed window.
+ Thread.sleep(10);
+
+ // A new connection from this key should prompt the user again.
+ runAdbTest(TEST_KEY_1, true, true, false);
+ }
+
+ @Test
+ public void testLastConnectionTimeCannotBeSetBack() throws Exception {
+ // When a device is first booted there is a possibility that the system time will be set to
+ // the build time of the system image. If a device is connected to a system during a reboot
+ // this could cause the connection time to be set in the past; if the device time is not
+ // corrected before the device is disconnected then a subsequent connection with the time
+ // corrected would appear as though the last connection time was beyond the allowed window,
+ // and the user would be required to authorize the connection again. This test verifies that
+ // the AdbKeyStore does not update the last connection time if it is less than the
+ // previously written connection time.
+
+ // Allow the key to connect with the 'Always allow' option selected
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Get the last connection time that was written to the key store.
+ long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+ // Attempt to set the last connection time to 1970
+ mKeyStore.setLastConnectionTime(TEST_KEY_1, 0);
+ assertEquals(
+ "The last connection time in the adb key store should not be set to a value less "
+ + "than the previous connection time",
+ lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
+
+ // Attempt to set the last connection time just beyond the allowed window.
+ mKeyStore.setLastConnectionTime(TEST_KEY_1,
+ Math.max(0, lastConnectionTime - (mKeyStore.getAllowedConnectionTime() + 1)));
+ assertEquals(
+ "The last connection time in the adb key store should not be set to a value less "
+ + "than the previous connection time",
+ lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
+ }
+
+ /**
+ * Runs an adb test with the provided configuration.
+ *
+ * @param key The base64 encoding of the key to be used during the test.
+ * @param allowKey boolean indicating whether the key should be allowed to connect.
+ * @param alwaysAllow boolean indicating whether the 'Always allow' option should be selected.
+ * @param autoAllowExpected boolean indicating whether the key is expected to be automatically
+ * allowed without user interaction.
+ */
+ private void runAdbTest(String key, boolean allowKey, boolean alwaysAllow,
+ boolean autoAllowExpected) throws Exception {
+ // if the key should not be automatically allowed then set up the activity
+ if (!autoAllowExpected) {
+ new AdbDebuggingManagerTestActivity.Configurator()
+ .setExpectedKey(key)
+ .setAllowKey(allowKey)
+ .setAlwaysAllow(alwaysAllow)
+ .setHandler(mHandler)
+ .setBlockingQueue(mBlockingQueue);
+ }
+ // send the message indicating a new key is attempting to connect
+ mHandler.obtainMessage(AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CONFIRM,
+ key).sendToTarget();
+ // if the key should not be automatically allowed then the ADB public key confirmation
+ // activity should be launched
+ if (!autoAllowExpected) {
+ TestResult activityResult = mBlockingQueue.poll(TIMEOUT, TIMEOUT_TIME_UNIT);
+ assertNotNull(
+ "The ADB public key confirmation activity did not complete within the timeout"
+ + " period", activityResult);
+ assertEquals("The ADB public key activity failed with result: " + activityResult,
+ TestResult.RESULT_ACTIVITY_LAUNCHED, activityResult.mReturnCode);
+ }
+ // If the activity was launched it should send a response back to the manager that would
+ // trigger a response to the thread, or if the key is a known valid key then a response
+ // should be sent back without requiring interaction with the activity.
+ TestResult threadResult = mBlockingQueue.poll(TIMEOUT, TIMEOUT_TIME_UNIT);
+ assertNotNull("A response was not sent to the thread within the timeout period",
+ threadResult);
+ // verify that the result is an expected message from the thread
+ assertEquals("An unexpected result was received: " + threadResult,
+ TestResult.RESULT_RESPONSE_RECEIVED, threadResult.mReturnCode);
+ assertEquals("The manager did not send the proper response for allowKey = " + allowKey,
+ allowKey ? RESPONSE_KEY_ALLOWED : RESPONSE_KEY_DENIED, threadResult.mMessage);
+ // if the key is not allowed or not always allowed verify it is not in the key store
+ if (!allowKey || !alwaysAllow) {
+ assertFalse(
+ "The key should not be allowed automatically on subsequent connection attempts",
+ mKeyStore.isKeyAuthorized(key));
+ }
+ }
+
+ /**
+ * Helper class that extends AdbDebuggingThread to receive the response from AdbDebuggingManager
+ * indicating whether the key should be allowed to connect.
+ */
+ class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread {
+ AdbDebuggingThreadTest() {
+ mManager.super();
+ }
+
+ @Override
+ public void sendResponse(String msg) {
+ TestResult result = new TestResult(TestResult.RESULT_RESPONSE_RECEIVED, msg);
+ try {
+ mBlockingQueue.put(result);
+ } catch (InterruptedException e) {
+ Log.e(TAG,
+ "Caught an InterruptedException putting the result in the queue: " + result,
+ e);
+ }
+ }
+ }
+
+ /**
+ * Contains the result for the current portion of the test along with any corresponding
+ * messages.
+ */
+ public static class TestResult {
+ public int mReturnCode;
+ public String mMessage;
+
+ public static final int RESULT_ACTIVITY_LAUNCHED = 1;
+ public static final int RESULT_UNEXPECTED_KEY = 2;
+ public static final int RESULT_RESPONSE_RECEIVED = 3;
+
+ public TestResult(int returnCode) {
+ this(returnCode, null);
+ }
+
+ public TestResult(int returnCode, String message) {
+ mReturnCode = returnCode;
+ mMessage = message;
+ }
+
+ @Override
+ public String toString() {
+ return "{mReturnCode = " + mReturnCode + ", mMessage = " + mMessage + "}";
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTestActivity.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTestActivity.java
new file mode 100644
index 0000000..1a9c180
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTestActivity.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.adb;
+
+import static com.android.server.adb.AdbDebuggingManagerTest.TestResult;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * Helper Activity used to test the AdbDebuggingManager's prompt to allow an adb key.
+ */
+public class AdbDebuggingManagerTestActivity extends Activity {
+
+ private static final String TAG = "AdbDebuggingManagerTestActivity";
+
+ /*
+ * Static values that must be set before each test to modify the behavior of the Activity.
+ */
+ private static AdbDebuggingManager.AdbDebuggingHandler sHandler;
+ private static boolean sAllowKey;
+ private static boolean sAlwaysAllow;
+ private static String sExpectedKey;
+ private static BlockingQueue sBlockingQueue;
+
+ /**
+ * Receives the Intent sent from the AdbDebuggingManager and sends the preconfigured response.
+ */
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ Intent intent = getIntent();
+ String key = intent.getStringExtra("key");
+ if (!key.equals(sExpectedKey)) {
+ TestResult result = new TestResult(TestResult.RESULT_UNEXPECTED_KEY, key);
+ postResult(result);
+ finish();
+ return;
+ }
+ // Post the result that the activity was successfully launched as expected and a response
+ // is being sent to let the test method know that it should move on to waiting for the next
+ // expected response from the AdbDebuggingManager.
+ TestResult result = new TestResult(
+ AdbDebuggingManagerTest.TestResult.RESULT_ACTIVITY_LAUNCHED);
+ postResult(result);
+
+ // Initialize the message based on the preconfigured values. If the key is accepted the
+ // AdbDebuggingManager expects the key to be in the obj field of the message, and if the
+ // user selects the 'Always allow' option the manager expects the arg1 field to be set to 1.
+ int messageType;
+ if (sAllowKey) {
+ messageType = AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_ALLOW;
+ } else {
+ messageType = AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DENY;
+ }
+ Message message = sHandler.obtainMessage(messageType);
+ message.obj = key;
+ if (sAlwaysAllow) {
+ message.arg1 = 1;
+ }
+ finish();
+ sHandler.sendMessage(message);
+ }
+
+ /**
+ * Posts the result of the activity to the test method.
+ */
+ private void postResult(TestResult result) {
+ try {
+ sBlockingQueue.put(result);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Caught an InterruptedException posting the result " + result, e);
+ }
+ }
+
+ /**
+ * Allows test methods to specify the behavior of the Activity before it is invoked by the
+ * AdbDebuggingManager.
+ */
+ public static class Configurator {
+
+ /**
+ * Sets the test handler to be used by this activity to send the configured response.
+ */
+ public Configurator setHandler(AdbDebuggingManager.AdbDebuggingHandler handler) {
+ sHandler = handler;
+ return this;
+ }
+
+ /**
+ * Sets whether the key should be allowed for this test.
+ */
+ public Configurator setAllowKey(boolean allow) {
+ sAllowKey = allow;
+ return this;
+ }
+
+ /**
+ * Sets whether the 'Always allow' option should be selected for this test.
+ */
+ public Configurator setAlwaysAllow(boolean alwaysAllow) {
+ sAlwaysAllow = alwaysAllow;
+ return this;
+ }
+
+ /**
+ * Sets the key that should be expected from the AdbDebuggingManager for this test.
+ */
+ public Configurator setExpectedKey(String expectedKey) {
+ sExpectedKey = expectedKey;
+ return this;
+ }
+
+ /**
+ * Sets the BlockingQueue that should be used to post the result of the Activity back to the
+ * test method.
+ */
+ public Configurator setBlockingQueue(BlockingQueue blockingQueue) {
+ sBlockingQueue = blockingQueue;
+ return this;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java
new file mode 100644
index 0000000..095a1de
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java
@@ -0,0 +1,1172 @@
+/*
+ * 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.backup.testutils;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ChangedPackages;
+import android.content.pm.IDexModuleRegisterCallback;
+import android.content.pm.IOnPermissionsChangeListener;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageDeleteObserver2;
+import android.content.pm.IPackageInstaller;
+import android.content.pm.IPackageManager;
+import android.content.pm.IPackageMoveObserver;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.KeySet;
+import android.content.pm.ModuleInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.SuspendDialogInfo;
+import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VersionedPackage;
+import android.content.pm.dex.IArtManager;
+import android.graphics.Bitmap;
+import android.os.IBinder;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import java.util.List;
+
+/**
+ * Stub for IPackageManager to use in tests.
+ */
+public class IPackageManagerStub implements IPackageManager {
+ public static PackageInfo sPackageInfo;
+ public static int sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+
+ @Override
+ public PackageInfo getPackageInfo(String packageName, int flags, int userId)
+ throws RemoteException {
+ return sPackageInfo;
+ }
+
+ @Override
+ public int getApplicationEnabledSetting(String packageName, int userId) throws RemoteException {
+ return sApplicationEnabledSetting;
+ }
+
+ @Override
+ public void checkPackageStartable(String packageName, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public boolean isPackageAvailable(String packageName, int userId) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public PackageInfo getPackageInfoVersioned(VersionedPackage versionedPackage, int flags,
+ int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public int getPackageUid(String packageName, int flags, int userId) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int[] getPackageGids(String packageName, int flags, int userId) throws RemoteException {
+ return new int[0];
+ }
+
+ @Override
+ public String[] currentToCanonicalPackageNames(String[] names) throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public String[] canonicalToCurrentPackageNames(String[] names) throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public PermissionInfo getPermissionInfo(String name, String packageName, int flags)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice queryPermissionsByGroup(String group, int flags)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public PermissionGroupInfo getPermissionGroupInfo(String name, int flags)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice getAllPermissionGroups(int flags) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ActivityInfo getActivityInfo(ComponentName className, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean activitySupportsIntent(ComponentName className, Intent intent,
+ String resolvedType)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public ActivityInfo getReceiverInfo(ComponentName className, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ServiceInfo getServiceInfo(ComponentName className, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ProviderInfo getProviderInfo(ComponentName className, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public int checkPermission(String permName, String pkgName, int userId) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int checkUidPermission(String permName, int uid) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public boolean addPermission(PermissionInfo info) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void removePermission(String name) throws RemoteException {
+
+ }
+
+ @Override
+ public void grantRuntimePermission(String packageName, String permissionName, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void revokeRuntimePermission(String packageName, String permissionName, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void resetRuntimePermissions() throws RemoteException {
+
+ }
+
+ @Override
+ public int getPermissionFlags(String permissionName, String packageName, int userId)
+ throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void updatePermissionFlags(String permissionName, String packageName, int flagMask,
+ int flagValues, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public boolean shouldShowRequestPermissionRationale(String permissionName, String packageName,
+ int userId) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean isProtectedBroadcast(String actionName) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public int checkSignatures(String pkg1, String pkg2) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int checkUidSignatures(int uid1, int uid2) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public List<String> getAllPackages() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public String[] getPackagesForUid(int uid) throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public String getNameForUid(int uid) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public String[] getNamesForUids(int[] uids) throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public int getUidForSharedUser(String sharedUserName) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int getFlagsForUid(int uid) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int getPrivateFlagsForUid(int uid) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public boolean isUidPrivileged(int uid) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public String[] getAppOpPermissionPackages(String permissionName) throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public ResolveInfo resolveIntent(Intent intent, String resolvedType, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ResolveInfo findPersistentPreferredActivity(Intent intent, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean canForwardTo(Intent intent, String resolvedType, int sourceUserId,
+ int targetUserId) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public ParceledListSlice queryIntentActivities(Intent intent, String resolvedType, int flags,
+ int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice queryIntentActivityOptions(ComponentName caller, Intent[] specifics,
+ String[] specificTypes, Intent intent, String resolvedType, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice queryIntentReceivers(Intent intent, String resolvedType, int flags,
+ int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice queryIntentServices(Intent intent, String resolvedType, int flags,
+ int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice queryIntentContentProviders(Intent intent, String resolvedType,
+ int flags, int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice getInstalledPackages(int flags, int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice getPackagesHoldingPermissions(String[] permissions, int flags,
+ int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice getInstalledApplications(int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice getPersistentApplications(int flags) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ProviderInfo resolveContentProvider(String name, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void querySyncProviders(List<String> outNames, List<ProviderInfo> outInfo)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public ParceledListSlice queryContentProviders(String processName, int uid, int flags,
+ String metaDataKey) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice queryInstrumentation(String targetPackage, int flags)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void finishPackageInstall(int token, boolean didLaunch) throws RemoteException {
+
+ }
+
+ @Override
+ public void setInstallerPackageName(String targetPackage, String installerPackageName)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void setApplicationCategoryHint(String packageName, int categoryHint,
+ String callerPackageName) throws RemoteException {
+
+ }
+
+ @Override
+ public void deletePackageAsUser(String packageName, int versionCode,
+ IPackageDeleteObserver observer, int userId, int flags) throws RemoteException {
+
+ }
+
+ @Override
+ public void deletePackageVersioned(VersionedPackage versionedPackage,
+ IPackageDeleteObserver2 observer, int userId, int flags) throws RemoteException {
+
+ }
+
+ @Override
+ public String getInstallerPackageName(String packageName) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void resetApplicationPreferences(int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public ResolveInfo getLastChosenActivity(Intent intent, String resolvedType, int flags)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void setLastChosenActivity(Intent intent, String resolvedType, int flags,
+ IntentFilter filter, int match, ComponentName activity) throws RemoteException {
+
+ }
+
+ @Override
+ public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set,
+ ComponentName activity, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void replacePreferredActivity(IntentFilter filter, int match, ComponentName[] set,
+ ComponentName activity, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void clearPackagePreferredActivities(String packageName) throws RemoteException {
+
+ }
+
+ @Override
+ public int getPreferredActivities(List<IntentFilter> outFilters,
+ List<ComponentName> outActivities, String packageName) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void addPersistentPreferredActivity(IntentFilter filter, ComponentName activity,
+ int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void clearPackagePersistentPreferredActivities(String packageName, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void addCrossProfileIntentFilter(IntentFilter intentFilter, String ownerPackage,
+ int sourceUserId, int targetUserId, int flags) throws RemoteException {
+
+ }
+
+ @Override
+ public void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public String[] setDistractingPackageRestrictionsAsUser(String[] packageNames,
+ int restrictionFlags, int userId) throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
+ PersistableBundle appExtras, PersistableBundle launcherExtras, SuspendDialogInfo dialogInfo,
+ String callingPackage, int userId) throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getUnsuspendablePackagesForUser(String[] packageNames, int userId)
+ throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public boolean isPackageSuspendedForUser(String packageName, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public byte[] getPreferredActivityBackup(int userId) throws RemoteException {
+ return new byte[0];
+ }
+
+ @Override
+ public void restorePreferredActivities(byte[] backup, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public byte[] getDefaultAppsBackup(int userId) throws RemoteException {
+ return new byte[0];
+ }
+
+ @Override
+ public void restoreDefaultApps(byte[] backup, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public byte[] getIntentFilterVerificationBackup(int userId) throws RemoteException {
+ return new byte[0];
+ }
+
+ @Override
+ public void restoreIntentFilterVerification(byte[] backup, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public byte[] getPermissionGrantBackup(int userId) throws RemoteException {
+ return new byte[0];
+ }
+
+ @Override
+ public void restorePermissionGrants(byte[] backup, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public ComponentName getHomeActivities(List<ResolveInfo> outHomeCandidates)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void setHomeActivity(ComponentName className, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags,
+ int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public int getComponentEnabledSetting(ComponentName componentName, int userId)
+ throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void setApplicationEnabledSetting(String packageName, int newState, int flags,
+ int userId,
+ String callingPackage) throws RemoteException {
+
+ }
+
+ @Override
+ public void logAppProcessStartIfNeeded(String processName, int uid, String seinfo,
+ String apkFile,
+ int pid) throws RemoteException {
+
+ }
+
+ @Override
+ public void flushPackageRestrictionsAsUser(int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void setPackageStoppedState(String packageName, boolean stopped, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void freeStorageAndNotify(String volumeUuid, long freeStorageSize, int storageFlags,
+ IPackageDataObserver observer) throws RemoteException {
+
+ }
+
+ @Override
+ public void freeStorage(String volumeUuid, long freeStorageSize, int storageFlags,
+ IntentSender pi) throws RemoteException {
+
+ }
+
+ @Override
+ public void deleteApplicationCacheFiles(String packageName, IPackageDataObserver observer)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void deleteApplicationCacheFilesAsUser(String packageName, int userId,
+ IPackageDataObserver observer) throws RemoteException {
+
+ }
+
+ @Override
+ public void clearApplicationUserData(String packageName, IPackageDataObserver observer,
+ int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void clearApplicationProfileData(String packageName) throws RemoteException {
+
+ }
+
+ @Override
+ public void getPackageSizeInfo(String packageName, int userHandle,
+ IPackageStatsObserver observer)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public String[] getSystemSharedLibraryNames() throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public ParceledListSlice getSystemAvailableFeatures() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean hasSystemFeature(String name, int version) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void enterSafeMode() throws RemoteException {
+
+ }
+
+ @Override
+ public boolean isSafeMode() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void systemReady() throws RemoteException {
+
+ }
+
+ @Override
+ public boolean hasSystemUidErrors() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void performFstrimIfNeeded() throws RemoteException {
+
+ }
+
+ @Override
+ public void updatePackagesIfNeeded() throws RemoteException {
+
+ }
+
+ @Override
+ public void notifyPackageUse(String packageName, int reason) throws RemoteException {
+
+ }
+
+ @Override
+ public void notifyDexLoad(String loadingPackageName, List<String> classLoadersNames,
+ List<String> classPaths, String loaderIsa) throws RemoteException {
+
+ }
+
+ @Override
+ public void registerDexModule(String packageName, String dexModulePath, boolean isSharedModule,
+ IDexModuleRegisterCallback callback) throws RemoteException {
+
+ }
+
+ @Override
+ public boolean performDexOptMode(String packageName, boolean checkProfiles,
+ String targetCompilerFilter, boolean force, boolean bootComplete, String splitName)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean performDexOptSecondary(String packageName, String targetCompilerFilter,
+ boolean force) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean compileLayouts(String packageName) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void dumpProfiles(String packageName) throws RemoteException {
+
+ }
+
+ @Override
+ public void forceDexOpt(String packageName) throws RemoteException {
+
+ }
+
+ @Override
+ public boolean runBackgroundDexoptJob(List<String> packageNames) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void reconcileSecondaryDexFiles(String packageName) throws RemoteException {
+
+ }
+
+ @Override
+ public int getMoveStatus(int moveId) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void registerMoveCallback(IPackageMoveObserver callback) throws RemoteException {
+
+ }
+
+ @Override
+ public void unregisterMoveCallback(IPackageMoveObserver callback) throws RemoteException {
+
+ }
+
+ @Override
+ public int movePackage(String packageName, String volumeUuid) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int movePrimaryStorage(String volumeUuid) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public boolean addPermissionAsync(PermissionInfo info) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean setInstallLocation(int loc) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public int getInstallLocation() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int installExistingPackageAsUser(String packageName, int userId, int installFlags,
+ int installReason) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void verifyPendingInstall(int id, int verificationCode) throws RemoteException {
+
+ }
+
+ @Override
+ public void extendVerificationTimeout(int id, int verificationCodeAtTimeout,
+ long millisecondsToDelay) throws RemoteException {
+
+ }
+
+ @Override
+ public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public int getIntentVerificationStatus(String packageName, int userId) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public boolean updateIntentVerificationStatus(String packageName, int status, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public ParceledListSlice getIntentFilterVerifications(String packageName)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice getAllIntentFilters(String packageName) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean setDefaultBrowserPackageName(String packageName, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public String getDefaultBrowserPackageName(int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean isFirstBoot() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean isOnlyCoreApps() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean isUpgrade() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void setPermissionEnforced(String permission, boolean enforced) throws RemoteException {
+
+ }
+
+ @Override
+ public boolean isPermissionEnforced(String permission) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean isStorageLow() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean getApplicationHiddenSettingAsUser(String packageName, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void setSystemAppHiddenUntilInstalled(String packageName, boolean hidden)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public boolean setSystemAppInstallState(String packageName, boolean installed, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public IPackageInstaller getPackageInstaller() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean getBlockUninstallForUser(String packageName, int userId) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public KeySet getKeySetByAlias(String packageName, String alias) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public KeySet getSigningKeySet(String packageName) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean isPackageSignedByKeySet(String packageName, KeySet ks) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean isPackageSignedByKeySetExactly(String packageName, KeySet ks)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void grantDefaultPermissionsToEnabledCarrierApps(String[] packageNames, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void grantDefaultPermissionsToEnabledImsServices(String[] packageNames, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void grantDefaultPermissionsToEnabledTelephonyDataServices(String[] packageNames,
+ int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void revokeDefaultPermissionsFromDisabledTelephonyDataServices(String[] packageNames,
+ int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void grantDefaultPermissionsToActiveLuiApp(String packageName, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void revokeDefaultPermissionsFromLuiApps(String[] packageNames, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public boolean isPermissionRevokedByPolicy(String permission, String packageName, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public String getPermissionControllerPackageName() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice getInstantApps(int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public byte[] getInstantAppCookie(String packageName, int userId) throws RemoteException {
+ return new byte[0];
+ }
+
+ @Override
+ public boolean setInstantAppCookie(String packageName, byte[] cookie, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public Bitmap getInstantAppIcon(String packageName, int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean isInstantApp(String packageName, int userId) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean setRequiredForSystemUser(String packageName, boolean systemUserApp)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void setUpdateAvailable(String packageName, boolean updateAvaialble)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public String getServicesSystemSharedLibraryPackageName() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public String getSharedSystemSharedLibraryPackageName() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ChangedPackages getChangedPackages(int sequenceNumber, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean isPackageDeviceAdminOnAnyUser(String packageName) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public int getInstallReason(String packageName, int userId) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public ParceledListSlice getSharedLibraries(String packageName, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean canRequestPackageInstalls(String packageName, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void deletePreloadsFileCache() throws RemoteException {
+
+ }
+
+ @Override
+ public ComponentName getInstantAppResolverComponent() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ComponentName getInstantAppResolverSettingsComponent() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ComponentName getInstantAppInstallerComponent() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public String getInstantAppAndroidId(String packageName, int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public IArtManager getArtManager() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void setHarmfulAppWarning(String packageName, CharSequence warning, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public CharSequence getHarmfulAppWarning(String packageName, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean hasSigningCertificate(String packageName, byte[] signingCertificate, int flags)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean hasUidSigningCertificate(int uid, byte[] signingCertificate, int flags)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public String getSystemTextClassifierPackageName() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public String getWellbeingPackageName() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean isPackageStateProtected(String packageName, int userId) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void sendDeviceCustomizationReadyBroadcast() throws RemoteException {
+
+ }
+
+ @Override
+ public List<ModuleInfo> getInstalledModules(int flags) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ModuleInfo getModuleInfo(String packageName, int flags) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
index 525135c..7172752 100644
--- a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
+++ b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
@@ -48,11 +48,7 @@
@Override
public PackageInfo getPackageInfo(String packageName, int flags)
throws NameNotFoundException {
- if (sPackageInfo == null) {
- throw new NameNotFoundException();
- }
-
- return sPackageInfo;
+ return getPackageInfoAsUser(packageName, flags, UserHandle.USER_SYSTEM);
}
@Override
@@ -64,7 +60,11 @@
@Override
public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
throws NameNotFoundException {
- return null;
+ if (sPackageInfo == null) {
+ throw new NameNotFoundException();
+ }
+
+ return sPackageInfo;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
index 479a19b..a92b576 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
@@ -29,13 +29,14 @@
import android.content.pm.Signature;
import android.content.pm.SigningInfo;
import android.os.Process;
+import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.backup.UserBackupManagerService;
-import com.android.server.backup.testutils.PackageManagerStub;
+import com.android.server.backup.testutils.IPackageManagerStub;
import org.junit.Before;
import org.junit.Test;
@@ -53,13 +54,17 @@
private static final Signature SIGNATURE_3 = generateSignature((byte) 3);
private static final Signature SIGNATURE_4 = generateSignature((byte) 4);
- private PackageManagerStub mPackageManagerStub;
+ private IPackageManagerStub mPackageManagerStub;
private PackageManagerInternal mMockPackageManagerInternal;
+ private int mUserId;
+
@Before
public void setUp() throws Exception {
- mPackageManagerStub = new PackageManagerStub();
+ mPackageManagerStub = new IPackageManagerStub();
mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+
+ mUserId = UserHandle.USER_SYSTEM;
}
@Test
@@ -71,7 +76,7 @@
applicationInfo.packageName = TEST_PACKAGE_NAME;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isFalse();
}
@@ -86,7 +91,7 @@
applicationInfo.packageName = TEST_PACKAGE_NAME;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isFalse();
}
@@ -100,7 +105,7 @@
applicationInfo.packageName = UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isFalse();
}
@@ -114,11 +119,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isTrue();
}
@@ -132,11 +137,11 @@
applicationInfo.backupAgentName = null;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isTrue();
}
@@ -150,11 +155,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isTrue();
}
@@ -168,11 +173,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isFalse();
}
@@ -186,11 +191,11 @@
applicationInfo.backupAgentName = null;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isFalse();
}
@@ -204,11 +209,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isFalse();
}
@@ -222,10 +227,11 @@
applicationInfo.packageName = TEST_PACKAGE_NAME;
applicationInfo.enabled = true;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
- boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+ boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+ mUserId);
assertThat(isDisabled).isFalse();
}
@@ -239,10 +245,11 @@
applicationInfo.packageName = TEST_PACKAGE_NAME;
applicationInfo.enabled = false;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
- boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+ boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+ mUserId);
assertThat(isDisabled).isTrue();
}
@@ -255,10 +262,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
- boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+ boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+ mUserId);
assertThat(isDisabled).isFalse();
}
@@ -271,10 +279,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
- boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+ boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+ mUserId);
assertThat(isDisabled).isTrue();
}
@@ -287,10 +296,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
- boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+ boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+ mUserId);
assertThat(isDisabled).isTrue();
}
@@ -303,10 +313,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
- boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+ boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+ mUserId);
assertThat(isDisabled).isTrue();
}
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
index d43b677..5fcce67 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
@@ -44,6 +44,7 @@
import android.content.pm.SigningInfo;
import android.os.Bundle;
import android.os.Process;
+import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
@@ -88,12 +89,14 @@
private final PackageManagerStub mPackageManagerStub = new PackageManagerStub();
private Context mContext;
+ private int mUserId;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getContext();
+ mUserId = UserHandle.USER_SYSTEM;
}
@Test
@@ -146,7 +149,7 @@
fileMetadata);
RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
mPackageManagerStub, false /* allowApks */, fileMetadata, signatures,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
@@ -160,7 +163,7 @@
fileMetadata);
restorePolicy = tarBackupReader.chooseRestorePolicy(
mPackageManagerStub, false /* allowApks */, fileMetadata, signatures,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
@@ -223,7 +226,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
true /* allowApks */, new FileMetadata(), null /* signatures */,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
verifyZeroInteractions(mBackupManagerMonitorMock);
@@ -244,7 +247,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
true /* allowApks */, info, new Signature[0] /* signatures */,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -269,7 +272,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
true /* allowApks */, info, new Signature[0] /* signatures */,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -295,7 +298,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -320,7 +323,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -347,7 +350,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -381,7 +384,8 @@
PackageManagerStub.sPackageInfo = packageInfo;
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
+ false /* allowApks */, new FileMetadata(), signatures,
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -419,7 +423,8 @@
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
+ false /* allowApks */, new FileMetadata(), signatures,
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -456,7 +461,8 @@
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
+ false /* allowApks */, new FileMetadata(), signatures,
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -497,7 +503,8 @@
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, info, signatures, mMockPackageManagerInternal);
+ false /* allowApks */, info, signatures, mMockPackageManagerInternal,
+ mUserId);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -540,7 +547,8 @@
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- true /* allowApks */, info, signatures, mMockPackageManagerInternal);
+ true /* allowApks */, info, signatures, mMockPackageManagerInternal,
+ mUserId);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
verifyNoMoreInteractions(mBackupManagerMonitorMock);
@@ -579,7 +587,8 @@
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, info, signatures, mMockPackageManagerInternal);
+ false /* allowApks */, info, signatures, mMockPackageManagerInternal,
+ mUserId);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
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 38e8ac2..535198b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -206,6 +206,8 @@
mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_UID;
setUpUserManager();
+
+ when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);
}
private TransferOwnershipMetadataManager getMockTransferMetadataManager() {
@@ -836,6 +838,7 @@
MockUtils.checkIntent(intent),
MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID));
}
+
/**
* Test for: {@link DevicePolicyManager#setDeviceOwner} DO on system user installs successfully.
*/
@@ -2618,6 +2621,7 @@
when(getServices().lockPatternUtils
.isSeparateProfileChallengeEnabled(eq(PROFILE_USER))).thenReturn(false);
dpmi.reportSeparateProfileChallengeChanged(PROFILE_USER);
+ when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);
verifyScreenTimeoutCall(Long.MAX_VALUE, PROFILE_USER);
verifyScreenTimeoutCall(10L , UserHandle.USER_SYSTEM);
@@ -4233,6 +4237,41 @@
assertTrue(dpm.isActivePasswordSufficient());
}
+ public void testIsActivePasswordSufficient_noLockScreen() throws Exception {
+ // If there is no lock screen, the password is considered empty no matter what, because
+ // it provides no security.
+ when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ mContext.packageName = admin1.getPackageName();
+ setupDeviceOwner();
+
+ // If no password requirements are set, isActivePasswordSufficient should succeed.
+ assertTrue(dpm.isActivePasswordSufficient());
+
+ // Now set some password quality requirements.
+ dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+
+ reset(mContext.spiedContext);
+ final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
+ PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics(
+ DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
+ 8, 2,
+ 6, 1,
+ 0, 1);
+ // This should be ignored, as there is no lock screen.
+ dpm.setActivePasswordState(passwordMetricsNoSymbols, userHandle);
+ dpm.reportPasswordChanged(userHandle);
+
+ // No broadcast should be sent.
+ verify(mContext.spiedContext, times(0)).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED),
+ MockUtils.checkUserHandle(userHandle));
+
+ // The active (nonexistent) password doesn't comply with the requirements.
+ assertFalse(dpm.isActivePasswordSufficient());
+ }
+
private void setActivePasswordState(PasswordMetrics passwordMetrics)
throws Exception {
final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
@@ -5200,6 +5239,45 @@
assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity());
}
+ public void testCrossProfileCalendarPackages_initiallyEmpty() {
+ setAsProfileOwner(admin1);
+ final Set<String> packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet());
+ }
+
+ public void testCrossProfileCalendarPackages_reopenDpms() {
+ setAsProfileOwner(admin1);
+ dpm.setCrossProfileCalendarPackages(admin1, null);
+ Set<String> packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertTrue(packages == null);
+ initializeDpms();
+ packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertTrue(packages == null);
+
+ dpm.setCrossProfileCalendarPackages(admin1, Collections.emptySet());
+ packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet());
+ initializeDpms();
+ packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet());
+
+ final String dummyPackageName = "test";
+ final Set<String> testPackages = new ArraySet<String>(Arrays.asList(dummyPackageName));
+ dpm.setCrossProfileCalendarPackages(admin1, testPackages);
+ packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertCrossProfileCalendarPackagesEqual(packages, testPackages);
+ initializeDpms();
+ packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertCrossProfileCalendarPackagesEqual(packages, testPackages);
+ }
+
+ private void assertCrossProfileCalendarPackagesEqual(Set<String> expected, Set<String> actual) {
+ assertTrue(expected != null);
+ assertTrue(actual != null);
+ assertTrue(expected.containsAll(actual));
+ assertTrue(actual.containsAll(expected));
+ }
+
private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) {
final long ident = mServiceContext.binder.clearCallingIdentity();
mServiceContext.binder.callingUid =
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 3a6cdc2..a89198a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -147,12 +147,12 @@
}
@Override
- void writeStringSetting(String key, String value) {
+ void writeStringSystemProperty(String key, String value) {
// do nothing
}
@Override
- boolean readBooleanSetting(String key, boolean defVal) {
+ boolean readBooleanSystemProperty(String key, boolean defVal) {
switch (key) {
case Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE:
return mMutingEnabled;
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index 0328621..8afc3d3 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -22,7 +22,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -58,8 +57,7 @@
// Make sure that there is a short-circuit for DEFAULT_DISPLAY.
assertEquals(DEFAULT_DISPLAY,
InputMethodManagerService.computeImeDisplayIdForTarget(
- DEFAULT_DISPLAY, false /* isVrImeStarted */,
- sMustNotBeCalledChecker));
+ DEFAULT_DISPLAY, sMustNotBeCalledChecker));
}
@Test
@@ -67,17 +65,7 @@
// Make sure that there is a short-circuit for INVALID_DISPLAY.
assertEquals(DEFAULT_DISPLAY,
InputMethodManagerService.computeImeDisplayIdForTarget(
- INVALID_DISPLAY, false /* isVrImeStarted */,
- sMustNotBeCalledChecker));
- }
-
- @Test
- public void testComputeImeDisplayId_VrIme() {
- // Make sure that there is a short-circuit for VR IME.
- assertEquals(DEFAULT_DISPLAY,
- InputMethodManagerService.computeImeDisplayIdForTarget(
- SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, true /* isVrImeStarted */,
- sMustNotBeCalledChecker));
+ INVALID_DISPLAY, sMustNotBeCalledChecker));
}
@Test
@@ -86,8 +74,7 @@
// Make sure IME displayId is DEFAULT_DISPLAY.
assertEquals(DEFAULT_DISPLAY,
InputMethodManagerService.computeImeDisplayIdForTarget(
- NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, false /* isVrImeStarted */,
- sChecker));
+ NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
}
@Test
@@ -96,7 +83,6 @@
// Make sure IME displayId is the same display.
assertEquals(SYSTEM_DECORATION_SUPPORT_DISPLAY_ID,
InputMethodManagerService.computeImeDisplayIdForTarget(
- SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, false /* isVrImeStarted */,
- sChecker));
+ SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 2dc3510..cf89cb8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -37,8 +37,8 @@
import android.os.IProgressListener;
import android.os.RemoteException;
import android.os.UserManager;
-import android.os.storage.StorageManager;
import android.os.storage.IStorageManager;
+import android.os.storage.StorageManager;
import android.security.KeyStore;
import android.test.AndroidTestCase;
@@ -46,6 +46,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -85,6 +86,8 @@
KeyStore mKeyStore;
MockSyntheticPasswordManager mSpManager;
IAuthSecret mAuthSecretService;
+ WindowManagerInternal mMockWindowManager;
+ protected boolean mHasSecureLockScreen;
@Override
protected void setUp() throws Exception {
@@ -97,10 +100,13 @@
mActivityManager = mock(IActivityManager.class);
mDevicePolicyManager = mock(DevicePolicyManager.class);
mDevicePolicyManagerInternal = mock(DevicePolicyManagerInternal.class);
+ mMockWindowManager = mock(WindowManagerInternal.class);
LocalServices.removeServiceForTest(LockSettingsInternal.class);
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+ LocalServices.removeServiceForTest(WindowManagerInternal.class);
LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal);
+ LocalServices.addService(WindowManagerInternal.class, mMockWindowManager);
mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager,
mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class),
@@ -114,11 +120,17 @@
storageDir.mkdirs();
}
+ mHasSecureLockScreen = true;
mLockPatternUtils = new LockPatternUtils(mContext) {
@Override
public ILockSettings getLockSettings() {
return mService;
}
+
+ @Override
+ public boolean hasSecureLockScreen() {
+ return mHasSecureLockScreen;
+ }
};
mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService,
mUserManager);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index e12f6d3..5124803 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -26,13 +26,12 @@
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.service.gatekeeper.GateKeeperResponse;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
import com.android.server.locksettings.FakeGateKeeperService.VerifyHandle;
+import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
/**
* runtest frameworks-services -c com.android.server.locksettings.LockSettingsServiceTests
@@ -54,11 +53,21 @@
PASSWORD_QUALITY_ALPHABETIC);
}
+ public void testCreatePasswordFailsWithoutLockScreen() throws RemoteException {
+ testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "password",
+ CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
+ }
+
public void testCreatePatternPrimaryUser() throws RemoteException {
testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN,
PASSWORD_QUALITY_SOMETHING);
}
+ public void testCreatePatternFailsWithoutLockScreen() throws RemoteException {
+ testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "123456789",
+ CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING);
+ }
+
public void testChangePasswordPrimaryUser() throws RemoteException {
testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
"asdfghjk", CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
@@ -198,6 +207,21 @@
assertVerifyCredentials(userId, credential, type, -1);
}
+ private void testCreateCredentialFailsWithoutLockScreen(
+ int userId, String credential, int type, int quality) throws RemoteException {
+ mHasSecureLockScreen = false;
+
+ try {
+ mService.setLockCredential(credential, type, null, quality, userId);
+ fail("An exception should have been thrown.");
+ } catch (UnsupportedOperationException e) {
+ // Success - the exception was expected.
+ }
+
+ assertFalse(mService.havePassword(userId));
+ assertFalse(mService.havePattern(userId));
+ }
+
private void testChangeCredentials(int userId, String newCredential, int newType,
String oldCredential, int oldType, int quality) throws RemoteException {
final long sid = 1234;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index a28a5a1..929c3b5 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -77,6 +78,7 @@
final Context context = InstrumentationRegistry.getTargetContext();
mUserId = ActivityManager.getCurrentUser();
mCommand = new LockSettingsShellCommand(mLockPatternUtils);
+ when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true);
}
@Test
@@ -103,6 +105,16 @@
}
@Test
+ public void testChangePin_noLockScreen() throws Exception {
+ when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+ assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-pin", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).hasSecureLockScreen();
+ verifyNoMoreInteractions(mLockPatternUtils);
+ }
+
+ @Test
public void testChangePassword() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
@@ -115,6 +127,16 @@
}
@Test
+ public void testChangePassword_noLockScreen() throws Exception {
+ when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+ assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-password", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).hasSecureLockScreen();
+ verifyNoMoreInteractions(mLockPatternUtils);
+ }
+
+ @Test
public void testChangePattern() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
@@ -126,6 +148,16 @@
}
@Test
+ public void testChangePattern_noLockScreen() throws Exception {
+ when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+ assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-pattern", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).hasSecureLockScreen();
+ verifyNoMoreInteractions(mLockPatternUtils);
+ }
+
+ @Test
public void testClear() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 94e02bc..0595a5b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -40,10 +40,10 @@
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
import com.android.server.locksettings.SyntheticPasswordManager.PasswordData;
-import java.util.ArrayList;
-
import org.mockito.ArgumentCaptor;
+import java.util.ArrayList;
+
/**
* runtest frameworks-services -c com.android.server.locksettings.SyntheticPasswordTests
@@ -448,6 +448,37 @@
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
}
+ public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception {
+ final String password = "password";
+ final String pattern = "123654";
+ final String token = "some-high-entropy-secure-token";
+
+ mHasSecureLockScreen = false;
+ enableSyntheticPassword();
+ long handle = mLocalService.addEscrowToken(token.getBytes(), PRIMARY_USER_ID);
+ assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ try {
+ mLocalService.setLockCredentialWithToken(password,
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, token.getBytes(),
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ fail("An exception should have been thrown.");
+ } catch (UnsupportedOperationException e) {
+ // Success - the exception was expected.
+ }
+ assertFalse(mService.havePassword(PRIMARY_USER_ID));
+
+ try {
+ mLocalService.setLockCredentialWithToken(pattern,
+ LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, token.getBytes(),
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ fail("An exception should have been thrown.");
+ } catch (UnsupportedOperationException e) {
+ // Success - the exception was expected.
+ }
+ assertFalse(mService.havePattern(PRIMARY_USER_ID));
+ }
+
public void testgetHashFactorPrimaryUser() throws RemoteException {
final String password = "password";
mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index bc1f798..6845f15 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -43,6 +43,7 @@
import android.app.ActivityManagerInternal;
import android.app.IUidObserver;
import android.app.Person;
+import android.app.admin.DevicePolicyManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
@@ -143,8 +144,10 @@
switch (name) {
case Context.USER_SERVICE:
return mMockUserManager;
+ case Context.DEVICE_POLICY_SERVICE:
+ return mMockDevicePolicyManager;
}
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("Couldn't find system service: " + name);
}
@Override
@@ -610,6 +613,7 @@
protected PackageManager mMockPackageManager;
protected PackageManagerInternal mMockPackageManagerInternal;
protected UserManager mMockUserManager;
+ protected DevicePolicyManager mMockDevicePolicyManager;
protected UserManagerInternal mMockUserManagerInternal;
protected UsageStatsManagerInternal mMockUsageStatsManagerInternal;
protected ActivityManagerInternal mMockActivityManagerInternal;
@@ -750,6 +754,7 @@
mMockPackageManager = mock(PackageManager.class);
mMockPackageManagerInternal = mock(PackageManagerInternal.class);
mMockUserManager = mock(UserManager.class);
+ mMockDevicePolicyManager = mock(DevicePolicyManager.class);
mMockUserManagerInternal = mock(UserManagerInternal.class);
mMockUsageStatsManagerInternal = mock(UsageStatsManagerInternal.class);
mMockActivityManagerInternal = mock(ActivityManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
new file mode 100644
index 0000000..73e9613
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -0,0 +1,305 @@
+/*
+ * 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.pm;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+import android.content.pm.PackageInstaller;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.FastXmlSerializer;
+
+import libcore.io.IoUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class PackageInstallerSessionTest {
+ private File mTmpDir;
+ private AtomicFile mSessionsFile;
+ private static final String TAG_SESSIONS = "sessions";
+
+ @Mock
+ PackageManagerService mMockPackageManagerInternal;
+
+ @Before
+ public void setUp() throws Exception {
+ mTmpDir = IoUtils.createTemporaryDirectory("PackageInstallerSessionTest");
+ mSessionsFile = new AtomicFile(
+ new File(mTmpDir.getAbsolutePath() + "/sessions.xml"), "package-session");
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testWriteAndRestoreSessionXmlSimpleSession() {
+ PackageInstallerSession session = createSimpleSession();
+ dumpSession(session);
+ List<PackageInstallerSession> restored = restoreSessions();
+ assertEquals(1, restored.size());
+ assertSessionsEquivalent(session, restored.get(0));
+ }
+
+ @Test
+ public void testWriteAndRestoreSessionXmlStagedSession() {
+ PackageInstallerSession session = createStagedSession();
+ dumpSession(session);
+ List<PackageInstallerSession> restored = restoreSessions();
+ assertEquals(1, restored.size());
+ assertSessionsEquivalent(session, restored.get(0));
+ }
+
+ @Test
+ public void testWriteAndRestoreSessionXmlGrantedPermission() {
+ PackageInstallerSession session = createSessionWithGrantedPermissions();
+ dumpSession(session);
+ List<PackageInstallerSession> restored = restoreSessions();
+ assertEquals(1, restored.size());
+ assertSessionsEquivalent(session, restored.get(0));
+ }
+
+ @Test
+ public void testWriteAndRestoreSessionXmlMultiPackageSessions() {
+ PackageInstallerSession session = createMultiPackageParentSession(123, new int[]{234, 345});
+ PackageInstallerSession childSession1 = createMultiPackageChildSession(234, 123);
+ PackageInstallerSession childSession2 = createMultiPackageChildSession(345, 123);
+ List<PackageInstallerSession> sessionGroup =
+ Arrays.asList(session, childSession1, childSession2);
+ dumpSessions(sessionGroup);
+ List<PackageInstallerSession> restored = restoreSessions();
+ assertEquals(3, restored.size());
+ assertSessionsEquivalent(sessionGroup, restored);
+ }
+
+ private PackageInstallerSession createSimpleSession() {
+ return createSession(false, false, 123, false, PackageInstaller.SessionInfo.INVALID_ID,
+ null);
+ }
+
+ private PackageInstallerSession createStagedSession() {
+ return createSession(true, false, 123, false, PackageInstaller.SessionInfo.INVALID_ID,
+ null);
+ }
+
+ private PackageInstallerSession createSessionWithGrantedPermissions() {
+ return createSession(false, true, 123, false, PackageInstaller.SessionInfo.INVALID_ID,
+ null);
+ }
+
+ private PackageInstallerSession createMultiPackageParentSession(int sessionId,
+ int[] childSessionIds) {
+ return createSession(false, false, sessionId, true,
+ PackageInstaller.SessionInfo.INVALID_ID, childSessionIds);
+ }
+
+ private PackageInstallerSession createMultiPackageChildSession(int sessionId,
+ int parentSessionId) {
+ return createSession(false, false, sessionId, false, parentSessionId, null);
+ }
+
+ private PackageInstallerSession createSession(boolean staged, boolean withGrantedPermissions,
+ int sessionId, boolean isMultiPackage,
+ int parentSessionId, int[] childSessionIds) {
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ if (staged) {
+ params.isStaged = true;
+ }
+ if (withGrantedPermissions) {
+ params.grantedRuntimePermissions = new String[]{"permission1", "permission2"};
+ }
+ if (isMultiPackage) {
+ params.isMultiPackage = true;
+ }
+ return new PackageInstallerSession(
+ /* callback */ null,
+ /* context */null,
+ /* pm */ mMockPackageManagerInternal,
+ /* sessionProvider */ null,
+ /* looper */ BackgroundThread.getHandler().getLooper(),
+ /* stagingManager */ null,
+ /* sessionId */ sessionId,
+ /* userId */ 456,
+ /* installerPackageName */ "testInstaller",
+ /* installerUid */ -1,
+ /* sessionParams */ params,
+ /* createdMillis */ 0L,
+ /* stageDir */ mTmpDir,
+ /* stageCid */ null,
+ /* prepared */ true,
+ /* sealed */ false, // Setting to true would trigger some PM logic.
+ /* childSessionIds */ childSessionIds != null ? childSessionIds : new int[0],
+ /* parentSessionId */ parentSessionId,
+ /* isReady */ staged ? true : false,
+ /* isFailed */ false,
+ /* isApplied */false,
+ /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED);
+ }
+
+ private void dumpSession(PackageInstallerSession session) {
+ dumpSessions(Arrays.asList(session));
+ }
+
+ private void dumpSessions(List<PackageInstallerSession> sessions) {
+ FileOutputStream fos = null;
+ try {
+ fos = mSessionsFile.startWrite();
+
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.startTag(null, TAG_SESSIONS);
+ for (PackageInstallerSession session : sessions) {
+ session.write(out, mTmpDir);
+ }
+ out.endTag(null, TAG_SESSIONS);
+ out.endDocument();
+
+ mSessionsFile.finishWrite(fos);
+ Slog.d("PackageInstallerSessionTest", new String(mSessionsFile.readFully()));
+ } catch (IOException e) {
+ if (fos != null) {
+ mSessionsFile.failWrite(fos);
+ }
+ }
+ }
+
+ // This is roughly the logic used in PackageInstallerService to read the session. Note that
+ // this test stresses readFromXml method from PackageInstallerSession, and doesn't cover the
+ // PackageInstallerService portion of the parsing.
+ private List<PackageInstallerSession> restoreSessions() {
+ List<PackageInstallerSession> ret = new ArrayList<>();
+ FileInputStream fis = null;
+ try {
+ fis = mSessionsFile.openRead();
+ final XmlPullParser in = Xml.newPullParser();
+ in.setInput(fis, StandardCharsets.UTF_8.name());
+
+ int type;
+ while ((type = in.next()) != END_DOCUMENT) {
+ if (type == START_TAG) {
+ final String tag = in.getName();
+ if (PackageInstallerSession.TAG_SESSION.equals(tag)) {
+ final PackageInstallerSession session;
+ try {
+ session = PackageInstallerSession.readFromXml(in, null,
+ null, mMockPackageManagerInternal,
+ BackgroundThread.getHandler().getLooper(), null,
+ mTmpDir, null);
+ ret.add(session);
+ } catch (Exception e) {
+ Slog.e("PackageInstallerSessionTest", "Exception ", e);
+ continue;
+ }
+ }
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // Missing sessions are okay, probably first boot
+ } catch (IOException | XmlPullParserException e) {
+
+ } finally {
+ IoUtils.closeQuietly(fis);
+ }
+ return ret;
+ }
+
+ private void assertSessionParamsEquivalent(PackageInstaller.SessionParams expected,
+ PackageInstaller.SessionParams actual) {
+ assertEquals(expected.mode, actual.mode);
+ assertEquals(expected.installFlags, actual.installFlags);
+ assertEquals(expected.installLocation, actual.installLocation);
+ assertEquals(expected.installReason, actual.installReason);
+ assertEquals(expected.sizeBytes, actual.sizeBytes);
+ assertEquals(expected.appPackageName, actual.appPackageName);
+ assertEquals(expected.appIcon, actual.appIcon);
+ assertEquals(expected.originatingUri, actual.originatingUri);
+ assertEquals(expected.originatingUid, actual.originatingUid);
+ assertEquals(expected.referrerUri, actual.referrerUri);
+ assertEquals(expected.abiOverride, actual.abiOverride);
+ assertEquals(expected.volumeUuid, actual.volumeUuid);
+ assertArrayEquals(expected.grantedRuntimePermissions, actual.grantedRuntimePermissions);
+ assertEquals(expected.installerPackageName, actual.installerPackageName);
+ assertEquals(expected.isMultiPackage, actual.isMultiPackage);
+ assertEquals(expected.isStaged, actual.isStaged);
+ }
+
+ private void assertSessionsEquivalent(List<PackageInstallerSession> expected,
+ List<PackageInstallerSession> actual) {
+ assertEquals(expected.size(), actual.size());
+ for (PackageInstallerSession expectedSession : expected) {
+ boolean foundSession = false;
+ for (PackageInstallerSession actualSession : actual) {
+ if (expectedSession.sessionId == actualSession.sessionId) {
+ // We should only encounter each expected session once.
+ assertFalse(foundSession);
+ foundSession = true;
+ assertSessionsEquivalent(expectedSession, actualSession);
+ }
+ }
+ assertTrue(foundSession);
+ }
+ }
+
+ private void assertSessionsEquivalent(PackageInstallerSession expected,
+ PackageInstallerSession actual) {
+ assertEquals(expected.sessionId, actual.sessionId);
+ assertEquals(expected.userId, actual.userId);
+ assertSessionParamsEquivalent(expected.params, actual.params);
+ assertEquals(expected.getInstallerUid(), actual.getInstallerUid());
+ assertEquals(expected.stageDir.getAbsolutePath(), actual.stageDir.getAbsolutePath());
+ assertEquals(expected.stageCid, actual.stageCid);
+ assertEquals(expected.isPrepared(), actual.isPrepared());
+ assertEquals(expected.isStaged(), actual.isStaged());
+ assertEquals(expected.isStagedSessionApplied(), actual.isStagedSessionApplied());
+ assertEquals(expected.isStagedSessionFailed(), actual.isStagedSessionFailed());
+ assertEquals(expected.isStagedSessionReady(), actual.isStagedSessionReady());
+ assertEquals(expected.getStagedSessionErrorCode(), actual.getStagedSessionErrorCode());
+ assertEquals(expected.isPrepared(), actual.isPrepared());
+ assertEquals(expected.isSealed(), actual.isSealed());
+ assertEquals(expected.isMultiPackage(), actual.isMultiPackage());
+ assertEquals(expected.hasParentSessionId(), actual.hasParentSessionId());
+ assertEquals(expected.getParentSessionId(), actual.getParentSessionId());
+ assertArrayEquals(expected.getChildSessionIds(), actual.getChildSessionIds());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
index f817e8e..6da202b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
@@ -16,8 +16,6 @@
package com.android.server.pm.dex;
-import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.atMost;
@@ -26,10 +24,12 @@
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
+import android.os.UserHandle;
import android.os.storage.StorageManager;
import androidx.test.filters.SmallTest;
@@ -56,40 +56,44 @@
public class DexLoggerTests {
private static final String OWNING_PACKAGE_NAME = "package.name";
private static final String VOLUME_UUID = "volUuid";
- private static final String DEX_PATH = "/bar/foo.jar";
+ private static final String FILE_PATH = "/bar/foo.jar";
private static final int STORAGE_FLAGS = StorageManager.FLAG_STORAGE_DE;
private static final int OWNER_UID = 43;
private static final int OWNER_USER_ID = 44;
// Obtained via: echo -n "foo.jar" | sha256sum
- private static final String DEX_FILENAME_HASH =
+ private static final String FILENAME_HASH =
"91D7B844D7CC9673748FF057D8DC83972280FC28537D381AA42015A9CF214B9F";
- private static final byte[] CONTENT_HASH_BYTES = new byte[] {
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
- 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
+ private static final byte[] CONTENT_HASH_BYTES = new byte[]{
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
};
private static final String CONTENT_HASH =
"0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20";
private static final byte[] EMPTY_BYTES = {};
- @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+ private static final String EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH =
+ "dcl:" + FILENAME_HASH;
+ private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH =
+ EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH + " " + CONTENT_HASH;
+ private static final String EXPECTED_MESSAGE_NATIVE_WITH_CONTENT_HASH =
+ "dcln:" + FILENAME_HASH + " " + CONTENT_HASH;
+
+ @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.LENIENT);
@Mock IPackageManager mPM;
@Mock Installer mInstaller;
- private PackageDynamicCodeLoading mPackageDynamicCodeLoading;
private DexLogger mDexLogger;
private final ListMultimap<Integer, String> mMessagesForUid = ArrayListMultimap.create();
private boolean mWriteTriggered = false;
- private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH =
- DEX_FILENAME_HASH + " " + CONTENT_HASH;
@Before
public void setup() throws Exception {
// Disable actually attempting to do file writes.
- mPackageDynamicCodeLoading = new PackageDynamicCodeLoading() {
+ PackageDynamicCodeLoading packageDynamicCodeLoading = new PackageDynamicCodeLoading() {
@Override
void maybeWriteAsync() {
mWriteTriggered = true;
@@ -102,13 +106,13 @@
};
// For test purposes capture log messages as well as sending to the event log.
- mDexLogger = new DexLogger(mPM, mInstaller, mPackageDynamicCodeLoading) {
+ mDexLogger = new DexLogger(mPM, mInstaller, packageDynamicCodeLoading) {
@Override
- void writeDclEvent(int uid, String message) {
- super.writeDclEvent(uid, message);
- mMessagesForUid.put(uid, message);
- }
- };
+ void writeDclEvent(String subtag, int uid, String message) {
+ super.writeDclEvent(subtag, uid, message);
+ mMessagesForUid.put(uid, subtag + ":" + message);
+ }
+ };
// Make the owning package exist in our mock PackageManager.
ApplicationInfo appInfo = new ApplicationInfo();
@@ -124,9 +128,9 @@
@Test
public void testOneLoader_ownFile_withFileHash() throws Exception {
- whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+ whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
- recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
@@ -139,13 +143,13 @@
@Test
public void testOneLoader_ownFile_noFileHash() throws Exception {
- whenFileIsHashed(DEX_PATH, doReturn(EMPTY_BYTES));
+ whenFileIsHashed(FILE_PATH, doReturn(EMPTY_BYTES));
- recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
- assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH);
+ assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH);
// File should be removed from the DCL list, since we can't hash it.
assertThat(mWriteTriggered).isTrue();
@@ -154,13 +158,14 @@
@Test
public void testOneLoader_ownFile_hashingFails() throws Exception {
- whenFileIsHashed(DEX_PATH, doThrow(new InstallerException("Intentional failure for test")));
+ whenFileIsHashed(FILE_PATH,
+ doThrow(new InstallerException("Intentional failure for test")));
- recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
- assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH);
+ assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH);
// File should be removed from the DCL list, since we can't hash it.
assertThat(mWriteTriggered).isTrue();
@@ -178,11 +183,23 @@
}
@Test
+ public void testOneLoader_pathTraversal() throws Exception {
+ String filePath = "/bar/../secret/foo.jar";
+ whenFileIsHashed(filePath, doReturn(CONTENT_HASH_BYTES));
+ setPackageUid(OWNING_PACKAGE_NAME, -1);
+
+ recordLoad(OWNING_PACKAGE_NAME, filePath);
+ mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
+
+ assertThat(mMessagesForUid).isEmpty();
+ }
+
+ @Test
public void testOneLoader_differentOwner() throws Exception {
- whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+ whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
setPackageUid("other.package.name", 1001);
- recordLoad("other.package.name", DEX_PATH);
+ recordLoad("other.package.name", FILE_PATH);
mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
assertThat(mMessagesForUid.keys()).containsExactly(1001);
@@ -192,10 +209,10 @@
@Test
public void testOneLoader_differentOwner_uninstalled() throws Exception {
- whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+ whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
setPackageUid("other.package.name", -1);
- recordLoad("other.package.name", DEX_PATH);
+ recordLoad("other.package.name", FILE_PATH);
mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
assertThat(mMessagesForUid).isEmpty();
@@ -203,22 +220,38 @@
}
@Test
+ public void testNativeCodeLoad() throws Exception {
+ whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
+
+ recordLoadNative(FILE_PATH);
+ mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
+
+ assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
+ assertThat(mMessagesForUid)
+ .containsEntry(OWNER_UID, EXPECTED_MESSAGE_NATIVE_WITH_CONTENT_HASH);
+
+ assertThat(mWriteTriggered).isFalse();
+ assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading())
+ .containsExactly(OWNING_PACKAGE_NAME);
+ }
+
+ @Test
public void testMultipleLoadersAndFiles() throws Exception {
String otherDexPath = "/bar/nosuchdir/foo.jar";
- whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+ whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
whenFileIsHashed(otherDexPath, doReturn(EMPTY_BYTES));
setPackageUid("other.package.name1", 1001);
setPackageUid("other.package.name2", 1002);
- recordLoad("other.package.name1", DEX_PATH);
+ recordLoad("other.package.name1", FILE_PATH);
recordLoad("other.package.name1", otherDexPath);
- recordLoad("other.package.name2", DEX_PATH);
- recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ recordLoad("other.package.name2", FILE_PATH);
+ recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
assertThat(mMessagesForUid.keys()).containsExactly(1001, 1001, 1002, OWNER_UID);
assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
- assertThat(mMessagesForUid).containsEntry(1001, DEX_FILENAME_HASH);
+ assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH);
assertThat(mMessagesForUid).containsEntry(1002, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
@@ -233,7 +266,7 @@
@Test
public void testUnknownOwner() {
reset(mPM);
- recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
mDexLogger.logDynamicCodeLoading("other.package.name");
assertThat(mMessagesForUid).isEmpty();
@@ -244,7 +277,7 @@
@Test
public void testUninstalledPackage() {
reset(mPM);
- recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
assertThat(mMessagesForUid).isEmpty();
@@ -262,7 +295,16 @@
}
private void recordLoad(String loadingPackageName, String dexPath) {
- mPackageDynamicCodeLoading.record(
- OWNING_PACKAGE_NAME, dexPath, FILE_TYPE_DEX, OWNER_USER_ID, loadingPackageName);
+ mDexLogger.recordDex(OWNER_USER_ID, dexPath, OWNING_PACKAGE_NAME, loadingPackageName);
+ mWriteTriggered = false;
+ }
+
+ private void recordLoadNative(String nativePath) throws Exception {
+ int loadingUid = UserHandle.getUid(OWNER_USER_ID, OWNER_UID);
+ String[] packageNames = { OWNING_PACKAGE_NAME };
+ when(mPM.getPackagesForUid(loadingUid)).thenReturn(packageNames);
+
+ mDexLogger.recordNative(loadingUid, nativePath);
+ mWriteTriggered = false;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index 7cd8cedd..2ddc71f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -55,6 +55,7 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -685,4 +686,68 @@
return mPackageInfo.applicationInfo.splitSourceDirs[length - 1];
}
}
+
+ private boolean shouldPackageRunOob(
+ boolean isDefaultEnabled, String defaultWhitelist, String overrideEnabled,
+ String overrideWhitelist, Collection<String> packageNamesInSameProcess) {
+ return DexManager.isPackageSelectedToRunOobInternal(
+ isDefaultEnabled, defaultWhitelist, overrideEnabled, overrideWhitelist,
+ packageNamesInSameProcess);
+ }
+
+ @Test
+ public void testOobPackageSelectionSwitch() {
+ // Feature is off by default, not overriden
+ assertFalse(shouldPackageRunOob(false, "ALL", null, null, null));
+
+ // Feature is off by default, overriden
+ assertTrue(shouldPackageRunOob(false, "ALL", "true", "ALL", null));
+ assertFalse(shouldPackageRunOob(false, "ALL", "false", null, null));
+ assertFalse(shouldPackageRunOob(false, "ALL", "false", "ALL", null));
+ assertFalse(shouldPackageRunOob(false, "ALL", "false", null, null));
+
+ // Feature is on by default, not overriden
+ assertTrue(shouldPackageRunOob(true, "ALL", null, null, null));
+ assertTrue(shouldPackageRunOob(true, "ALL", null, null, null));
+ assertTrue(shouldPackageRunOob(true, "ALL", null, "ALL", null));
+
+ // Feature is on by default, overriden
+ assertTrue(shouldPackageRunOob(true, "ALL", "true", null, null));
+ assertTrue(shouldPackageRunOob(true, "ALL", "true", "ALL", null));
+ assertFalse(shouldPackageRunOob(true, "ALL", "false", null, null));
+ assertFalse(shouldPackageRunOob(true, "ALL", "false", "ALL", null));
+ }
+
+ @Test
+ public void testOobPackageSelectionWhitelist() {
+ // Various whitelist of apps to run in OOB mode.
+ final String kWhitelistApp0 = "com.priv.app0";
+ final String kWhitelistApp1 = "com.priv.app1";
+ final String kWhitelistApp2 = "com.priv.app2";
+ final String kWhitelistApp1AndApp2 = "com.priv.app1,com.priv.app2";
+
+ // Packages that shares the targeting process.
+ final Collection<String> runningPackages = Arrays.asList("com.priv.app1", "com.priv.app2");
+
+ // Feature is off, whitelist does not matter
+ assertFalse(shouldPackageRunOob(false, kWhitelistApp0, null, null, runningPackages));
+ assertFalse(shouldPackageRunOob(false, kWhitelistApp1, null, null, runningPackages));
+ assertFalse(shouldPackageRunOob(false, "", null, kWhitelistApp1, runningPackages));
+ assertFalse(shouldPackageRunOob(false, "", null, "ALL", runningPackages));
+ assertFalse(shouldPackageRunOob(false, "ALL", null, "ALL", runningPackages));
+ assertFalse(shouldPackageRunOob(false, "ALL", null, "", runningPackages));
+
+ // Feature is on, app not in default or overridden whitelist
+ assertFalse(shouldPackageRunOob(true, kWhitelistApp0, null, null, runningPackages));
+ assertFalse(shouldPackageRunOob(true, "", null, kWhitelistApp0, runningPackages));
+ assertFalse(shouldPackageRunOob(true, "ALL", null, kWhitelistApp0, runningPackages));
+
+ // Feature is on, app in default or overridden whitelist
+ assertTrue(shouldPackageRunOob(true, kWhitelistApp1, null, null, runningPackages));
+ assertTrue(shouldPackageRunOob(true, kWhitelistApp2, null, null, runningPackages));
+ assertTrue(shouldPackageRunOob(true, kWhitelistApp1AndApp2, null, null, runningPackages));
+ assertTrue(shouldPackageRunOob(true, kWhitelistApp1, null, "ALL", runningPackages));
+ assertTrue(shouldPackageRunOob(true, "", null, kWhitelistApp1, runningPackages));
+ assertTrue(shouldPackageRunOob(true, "ALL", null, kWhitelistApp1, runningPackages));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
index b348aee..5d69bbd 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
@@ -18,12 +18,15 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.PendingIntent;
+import android.app.usage.UsageStatsManagerInternal;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.UserHandle;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
@@ -64,7 +67,7 @@
private static final long TIME_30_MIN = 30 * 60_000L;
private static final long TIME_10_MIN = 10 * 60_000L;
- private static final long TIME_1_MIN = 10 * 60_000L;
+ private static final long TIME_1_MIN = 1 * 60_000L;
private static final long MAX_OBSERVER_PER_UID = 10;
private static final long MIN_TIME_LIMIT = 4_000L;
@@ -128,6 +131,11 @@
}
@Override
+ protected long getAppUsageLimitObserverPerUidLimit() {
+ return MAX_OBSERVER_PER_UID;
+ }
+
+ @Override
protected long getMinTimeLimit() {
return MIN_TIME_LIMIT;
}
@@ -164,6 +172,16 @@
assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID2));
}
+ /** Verify app usage limit observer is added */
+ @Test
+ public void testAppUsageLimitObserver_AddObserver() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+ addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID2));
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/** Verify app usage observer is removed */
@Test
public void testAppUsageObserver_RemoveObserver() {
@@ -182,6 +200,15 @@
assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** Verify app usage limit observer is removed */
+ @Test
+ public void testAppUsageLimitObserver_RemoveObserver() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+ mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
+ assertFalse("Observer wasn't removed", hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/** Verify nothing happens when a nonexistent app usage observer is removed */
@Test
public void testAppUsageObserver_RemoveMissingObserver() {
@@ -218,6 +245,24 @@
assertFalse("Observer should not exist", hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** Verify nothing happens when a nonexistent app usage limit observer is removed */
+ @Test
+ public void testAppUsageLimitObserver_RemoveMissingObserver() {
+ assertFalse("Observer should not exist", hasAppUsageLimitObserver(UID, OBS_ID1));
+ try {
+ mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
+ } catch (Exception e) {
+ StringWriter sw = new StringWriter();
+ sw.write("Hit exception trying to remove nonexistent observer:\n");
+ sw.write(e.toString());
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ sw.write("\nTest Failed!");
+ fail(sw.toString());
+ }
+ assertFalse("Observer should not exist", hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/** Re-adding an observer should result in only one copy */
@Test
public void testAppUsageObserver_ObserverReAdd() {
@@ -242,22 +287,39 @@
assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** Re-adding an observer should result in only one copy */
+ @Test
+ public void testAppUsageLimitObserver_ObserverReAdd() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN);
+ assertTrue("Observer wasn't added",
+ getAppUsageLimitObserver(UID, OBS_ID1).getTimeLimitMs() == TIME_10_MIN);
+ mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
+ assertFalse("Observer wasn't removed", hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/** Different type observers can be registered to the same observerId value */
@Test
public void testAllObservers_ExclusiveObserverIds() {
addAppUsageObserver(OBS_ID1, GROUP1, TIME_10_MIN);
addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN);
assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
AppTimeLimitController.UsageGroup appUsageGroup = mController.getAppUsageGroup(UID,
OBS_ID1);
AppTimeLimitController.UsageGroup sessionUsageGroup = mController.getSessionUsageGroup(UID,
OBS_ID1);
+ AppTimeLimitController.UsageGroup appUsageLimitGroup = getAppUsageLimitObserver(
+ UID, OBS_ID1);
// Verify data still intact
assertEquals(TIME_10_MIN, appUsageGroup.getTimeLimitMs());
assertEquals(TIME_30_MIN, sessionUsageGroup.getTimeLimitMs());
+ assertEquals(TIME_10_MIN, appUsageLimitGroup.getTimeLimitMs());
}
/** Verify that usage across different apps within a group are added up */
@@ -299,7 +361,7 @@
@Test
public void testUsageSessionObserver_Accumulation() throws Exception {
setTime(0L);
- addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_10_MIN);
startUsage(PKG_SOC1);
// Add 10 mins
setTime(TIME_10_MIN);
@@ -330,6 +392,41 @@
assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
}
+ /** Verify that usage across different apps within a group are added up */
+ @Test
+ public void testAppUsageLimitObserver_Accumulation() throws Exception {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ startUsage(PKG_SOC1);
+ // Add 10 mins
+ setTime(TIME_10_MIN);
+ stopUsage(PKG_SOC1);
+
+ AppTimeLimitController.UsageGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+
+ long timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
+ assertEquals(TIME_10_MIN * 2, timeRemaining);
+
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN * 2);
+ stopUsage(PKG_SOC1);
+
+ timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
+ assertEquals(TIME_10_MIN, timeRemaining);
+
+ setTime(TIME_30_MIN);
+
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+
+ // Add a different package in the group
+ startUsage(PKG_GAME1);
+ setTime(TIME_30_MIN + TIME_10_MIN);
+ stopUsage(PKG_GAME1);
+
+ assertEquals(0, group.getTimeLimitMs() - group.getUsageTimeMs());
+ assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+ }
+
/** Verify that time limit does not get triggered due to a different app */
@Test
public void testAppUsageObserver_TimeoutOtherApp() throws Exception {
@@ -355,6 +452,18 @@
}
+ /** Verify that time limit does not get triggered due to a different app */
+ @Test
+ public void testAppUsageLimitObserver_TimeoutOtherApp() throws Exception {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L);
+ startUsage(PKG_SOC2);
+ assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ setTime(6_000L);
+ stopUsage(PKG_SOC2);
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+ }
+
/** Verify the timeout message is delivered at the right time */
@Test
public void testAppUsageObserver_Timeout() throws Exception {
@@ -385,6 +494,19 @@
assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** Verify the timeout message is delivered at the right time */
+ @Test
+ public void testAppUsageLimitObserver_Timeout() throws Exception {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L);
+ startUsage(PKG_SOC1);
+ setTime(6_000L);
+ assertTrue(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ // Verify that the observer was not removed
+ assertTrue(hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/** If an app was already running, make sure it is partially counted towards the time limit */
@Test
public void testAppUsageObserver_AlreadyRunning() throws Exception {
@@ -423,6 +545,25 @@
assertTrue(hasUsageSessionObserver(UID, OBS_ID2));
}
+ /** If an app was already running, make sure it is partially counted towards the time limit */
+ @Test
+ public void testAppUsageLimitObserver_AlreadyRunning() throws Exception {
+ setTime(TIME_10_MIN);
+ startUsage(PKG_GAME1);
+ setTime(TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+ setTime(TIME_30_MIN + TIME_10_MIN);
+ stopUsage(PKG_GAME1);
+ assertFalse(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+
+ startUsage(PKG_GAME2);
+ setTime(TIME_30_MIN + TIME_30_MIN);
+ stopUsage(PKG_GAME2);
+ assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was not removed
+ assertTrue(hasAppUsageLimitObserver(UID, OBS_ID2));
+ }
+
/** If watched app is already running, verify the timeout callback happens at the right time */
@Test
public void testAppUsageObserver_AlreadyRunningTimeout() throws Exception {
@@ -464,6 +605,24 @@
assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** If watched app is already running, verify the timeout callback happens at the right time */
+ @Test
+ public void testAppUsageLimitObserver_AlreadyRunningTimeout() throws Exception {
+ setTime(0);
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN);
+ // 10 second time limit
+ addAppUsageLimitObserver(OBS_ID1, GROUP_SOC, 10_000L);
+ setTime(TIME_10_MIN + 5_000L);
+ // Shouldn't call back in 6 seconds
+ assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ setTime(TIME_10_MIN + 10_000L);
+ // Should call back by 11 seconds (6 earlier + 5 now)
+ assertTrue(mLimitReachedLatch.await(5_000L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was not removed
+ assertTrue(hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/**
* Verify that App Time Limit Controller will limit the number of observerIds for app usage
* observers
@@ -525,6 +684,37 @@
assertTrue("Should have caused an IllegalStateException", receivedException);
}
+ /**
+ * Verify that App Time Limit Controller will limit the number of observerIds for app usage
+ * limit observers
+ */
+ @Test
+ public void testAppUsageLimitObserver_MaxObserverLimit() throws Exception {
+ boolean receivedException = false;
+ int ANOTHER_UID = UID + 1;
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID2, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID3, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID4, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID6, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID7, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID8, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID9, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID10, GROUP1, TIME_30_MIN);
+ // Readding an observer should not cause an IllegalStateException
+ addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+ // Adding an observer for a different uid shouldn't cause an IllegalStateException
+ mController.addAppUsageLimitObserver(
+ ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID);
+ try {
+ addAppUsageLimitObserver(OBS_ID11, GROUP1, TIME_30_MIN);
+ } catch (IllegalStateException ise) {
+ receivedException = true;
+ }
+ assertTrue("Should have caused an IllegalStateException", receivedException);
+ }
+
/** Verify that addAppUsageObserver minimum time limit is one minute */
@Test
public void testAppUsageObserver_MinimumTimeLimit() throws Exception {
@@ -553,6 +743,20 @@
assertTrue("Should have caused an IllegalArgumentException", receivedException);
}
+ /** Verify that addAppUsageLimitObserver minimum time limit is one minute */
+ @Test
+ public void testAppUsageLimitObserver_MinimumTimeLimit() throws Exception {
+ boolean receivedException = false;
+ // adding an observer with a one minute time limit should not cause an exception
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT);
+ try {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1);
+ } catch (IllegalArgumentException iae) {
+ receivedException = true;
+ }
+ assertTrue("Should have caused an IllegalArgumentException", receivedException);
+ }
+
/** Verify that concurrent usage from multiple apps in the same group will counted correctly */
@Test
public void testAppUsageObserver_ConcurrentUsage() throws Exception {
@@ -599,6 +803,29 @@
assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
}
+ /** Verify that concurrent usage from multiple apps in the same group will counted correctly */
+ @Test
+ public void testAppUsageLimitObserver_ConcurrentUsage() throws Exception {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ AppTimeLimitController.UsageGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+ startUsage(PKG_SOC1);
+ // Add 10 mins
+ setTime(TIME_10_MIN);
+
+ // Add a different package in the group will first package is still in use
+ startUsage(PKG_GAME1);
+ setTime(TIME_10_MIN * 2);
+ // Stop first package usage
+ stopUsage(PKG_SOC1);
+
+ setTime(TIME_30_MIN);
+ stopUsage(PKG_GAME1);
+
+ assertEquals(TIME_30_MIN, group.getUsageTimeMs());
+ assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+ }
+
/** Verify that a session will continue if usage starts again within the session threshold */
@Test
public void testUsageSessionObserver_ContinueSession() throws Exception {
@@ -737,6 +964,97 @@
assertFalse(hasAppUsageObserver(UID, OBS_ID1));
}
+ /** Verify app usage limit observer added correctly reports it being a group limit */
+ @Test
+ public void testAppUsageLimitObserver_IsGroupLimit() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+ assertNotNull("Observer wasn't added", group);
+ assertTrue("Observer didn't correctly report being a group limit",
+ group.isGroupLimit());
+ }
+
+ /** Verify app usage limit observer added correctly reports it being not a group limit */
+ @Test
+ public void testAppUsageLimitObserver_IsNotGroupLimit() {
+ addAppUsageLimitObserver(OBS_ID1, new String[]{PKG_PROD}, TIME_30_MIN);
+ AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+ assertNotNull("Observer wasn't added", group);
+ assertFalse("Observer didn't correctly report not being a group limit",
+ group.isGroupLimit());
+ }
+
+ /** Verify app usage limit observer added correctly reports its total usage limit */
+ @Test
+ public void testAppUsageLimitObserver_GetTotalUsageLimit() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+ assertNotNull("Observer wasn't added", group);
+ assertEquals("Observer didn't correctly report total usage limit",
+ TIME_30_MIN, group.getTotaUsageLimit());
+ }
+
+ /** Verify app usage limit observer added correctly reports its total usage limit */
+ @Test
+ public void testAppUsageLimitObserver_GetUsageRemaining() {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN);
+ stopUsage(PKG_SOC1);
+ AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+ assertNotNull("Observer wasn't added", group);
+ assertEquals("Observer didn't correctly report total usage limit",
+ TIME_10_MIN * 2, group.getUsageRemaining());
+ }
+
+ /** Verify the app usage limit observer with the smallest usage limit remaining is returned
+ * when querying the getAppUsageLimit API.
+ */
+ @Test
+ public void testAppUsageLimitObserver_GetAppUsageLimit() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+ UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
+ assertEquals("Observer with the smallest usage limit remaining wasn't returned",
+ TIME_10_MIN, group.getTotalUsageLimit());
+ }
+
+ /** Verify the app usage limit observer with the smallest usage limit remaining is returned
+ * when querying the getAppUsageLimit API.
+ */
+ @Test
+ public void testAppUsageLimitObserver_GetAppUsageLimitUsed() {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+ startUsage(PKG_GAME1);
+ setTime(TIME_10_MIN * 2 + TIME_1_MIN);
+ stopUsage(PKG_GAME1);
+ // PKG_GAME1 is only in GROUP1 but since we're querying for PCK_SOC1 which is
+ // in both groups, GROUP1 should be returned since it has a smaller time remaining
+ UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
+ assertEquals("Observer with the smallest usage limit remaining wasn't returned",
+ TIME_1_MIN * 9, group.getUsageRemaining());
+ }
+
+ /** Verify the app usage limit observer with the smallest usage limit remaining is returned
+ * when querying the getAppUsageLimit API.
+ */
+ @Test
+ public void testAppUsageLimitObserver_GetAppUsageLimitAllUsed() {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN);
+ stopUsage(PKG_SOC1);
+ // GROUP_SOC should be returned since it should be completely used up (0ms remaining)
+ UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
+ assertEquals("Observer with the smallest usage limit remaining wasn't returned",
+ 0L, group.getUsageRemaining());
+ }
+
private void startUsage(String packageName) {
mController.noteUsageStart(packageName, USER_ID);
}
@@ -759,6 +1077,10 @@
null, null, USER_ID);
}
+ private void addAppUsageLimitObserver(int observerId, String[] packages, long timeLimit) {
+ mController.addAppUsageLimitObserver(UID, observerId, packages, timeLimit, null, USER_ID);
+ }
+
/** Is there still an app usage observer by that id */
private boolean hasAppUsageObserver(int uid, int observerId) {
return mController.getAppUsageGroup(uid, observerId) != null;
@@ -769,6 +1091,20 @@
return mController.getSessionUsageGroup(uid, observerId) != null;
}
+ /** Is there still an app usage limit observer by that id */
+ private boolean hasAppUsageLimitObserver(int uid, int observerId) {
+ return mController.getAppUsageLimitGroup(uid, observerId) != null;
+ }
+
+ private AppTimeLimitController.AppUsageLimitGroup getAppUsageLimitObserver(
+ int uid, int observerId) {
+ return mController.getAppUsageLimitGroup(uid, observerId);
+ }
+
+ private UsageStatsManagerInternal.AppUsageLimitData getAppUsageLimit(String packageName) {
+ return mController.getAppUsageLimit(packageName, UserHandle.of(USER_ID));
+ }
+
private void setTime(long time) {
mUptimeMillis = time;
}
diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index 860656b..8d9b3cf 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -45,6 +45,8 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class UsageStatsDatabaseTest {
+
+ private static final int MAX_TESTED_VERSION = 4;
protected Context mContext;
private UsageStatsDatabase mUsageStatsDatabase;
private File mTestDir;
@@ -131,8 +133,8 @@
for (int i = 0; i < numberOfEvents; i++) {
Event event = new Event();
- final int packageInt = ((i / 3) % 7);
- event.mPackage = "fake.package.name" + packageInt; //clusters of 3 events from 7 "apps"
+ final int packageInt = ((i / 3) % 7); //clusters of 3 events from 7 "apps"
+ event.mPackage = "fake.package.name" + packageInt;
if (packageInt == 3) {
// Third app is an instant app
event.mFlags |= Event.FLAG_IS_PACKAGE_INSTANT_APP;
@@ -144,6 +146,13 @@
event.mEventType = i % (MAX_EVENT_TYPE + 1); //"random" event type
event.mInstanceId = instanceId;
+
+ final int rootPackageInt = (i % 5); // 5 "apps" start each task
+ event.mTaskRootPackage = "fake.package.name" + rootPackageInt;
+
+ final int rootClassInt = i % 6;
+ event.mTaskRootClass = ".fake.class.name" + rootClassInt;
+
switch (event.mEventType) {
case Event.CONFIGURATION_CHANGE:
//empty config,
@@ -163,7 +172,7 @@
break;
}
- mIntervalStats.events.insert(event);
+ mIntervalStats.addEvent(event);
mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType,
event.mInstanceId);
@@ -234,31 +243,40 @@
assertEquals(us1.mChooserCounts, us2.mChooserCounts);
}
- void compareUsageEvent(Event e1, Event e2, int debugId) {
- assertEquals(e1.mPackage, e2.mPackage, "Usage event " + debugId);
- assertEquals(e1.mClass, e2.mClass, "Usage event " + debugId);
- assertEquals(e1.mTimeStamp, e2.mTimeStamp, "Usage event " + debugId);
- assertEquals(e1.mEventType, e2.mEventType, "Usage event " + debugId);
- switch (e1.mEventType) {
- case Event.CONFIGURATION_CHANGE:
- assertEquals(e1.mConfiguration, e2.mConfiguration,
- "Usage event " + debugId + e2.mConfiguration.toString());
- break;
- case Event.SHORTCUT_INVOCATION:
- assertEquals(e1.mShortcutId, e2.mShortcutId, "Usage event " + debugId);
- break;
- case Event.STANDBY_BUCKET_CHANGED:
- assertEquals(e1.mBucketAndReason, e2.mBucketAndReason, "Usage event " + debugId);
- break;
- case Event.NOTIFICATION_INTERRUPTION:
- assertEquals(e1.mNotificationChannelId, e2.mNotificationChannelId,
- "Usage event " + debugId);
- break;
+ void compareUsageEvent(Event e1, Event e2, int debugId, int minVersion) {
+ switch (minVersion) {
+ case 4: // test fields added in version 4
+ assertEquals(e1.mInstanceId, e2.mInstanceId, "Usage event " + debugId);
+ assertEquals(e1.mTaskRootPackage, e2.mTaskRootPackage, "Usage event " + debugId);
+ assertEquals(e1.mTaskRootClass, e2.mTaskRootClass, "Usage event " + debugId);
+ // fallthrough
+ default:
+ assertEquals(e1.mPackage, e2.mPackage, "Usage event " + debugId);
+ assertEquals(e1.mClass, e2.mClass, "Usage event " + debugId);
+ assertEquals(e1.mTimeStamp, e2.mTimeStamp, "Usage event " + debugId);
+ assertEquals(e1.mEventType, e2.mEventType, "Usage event " + debugId);
+ switch (e1.mEventType) {
+ case Event.CONFIGURATION_CHANGE:
+ assertEquals(e1.mConfiguration, e2.mConfiguration,
+ "Usage event " + debugId + e2.mConfiguration.toString());
+ break;
+ case Event.SHORTCUT_INVOCATION:
+ assertEquals(e1.mShortcutId, e2.mShortcutId, "Usage event " + debugId);
+ break;
+ case Event.STANDBY_BUCKET_CHANGED:
+ assertEquals(e1.mBucketAndReason, e2.mBucketAndReason,
+ "Usage event " + debugId);
+ break;
+ case Event.NOTIFICATION_INTERRUPTION:
+ assertEquals(e1.mNotificationChannelId, e2.mNotificationChannelId,
+ "Usage event " + debugId);
+ break;
+ }
+ assertEquals(e1.mFlags, e2.mFlags);
}
- assertEquals(e1.mFlags, e2.mFlags);
}
- void compareIntervalStats(IntervalStats stats1, IntervalStats stats2) {
+ void compareIntervalStats(IntervalStats stats1, IntervalStats stats2, int minVersion) {
assertEquals(stats1.majorVersion, stats2.majorVersion);
assertEquals(stats1.minorVersion, stats2.minorVersion);
assertEquals(stats1.beginTime, stats2.beginTime);
@@ -311,7 +329,7 @@
} else {
assertEquals(stats1.events.size(), stats2.events.size());
for (int i = 0; i < stats1.events.size(); i++) {
- compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i);
+ compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i, minVersion);
}
}
}
@@ -326,7 +344,7 @@
mIntervalStatsVerifier);
assertEquals(1, stats.size());
- compareIntervalStats(mIntervalStats, stats.get(0));
+ compareIntervalStats(mIntervalStats, stats.get(0), MAX_TESTED_VERSION);
}
/**
@@ -359,8 +377,10 @@
mIntervalStatsVerifier);
assertEquals(1, stats.size());
+
+ final int minVersion = oldVersion < newVersion ? oldVersion : newVersion;
// The written and read IntervalStats should match
- compareIntervalStats(mIntervalStats, stats.get(0));
+ compareIntervalStats(mIntervalStats, stats.get(0), minVersion);
}
/**
@@ -401,7 +421,7 @@
if (mIntervalStats.events != null) mIntervalStats.events.clear();
// The written and read IntervalStats should match
- compareIntervalStats(mIntervalStats, stats.get(0));
+ compareIntervalStats(mIntervalStats, stats.get(0), version);
}
/**
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 94b21af..6222923 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -185,6 +185,9 @@
private NotificationChannel mTestNotificationChannel = new NotificationChannel(
TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+
+ private static final int NOTIFICATION_LOCATION_UNKNOWN = 0;
+
@Mock
private NotificationListeners mListeners;
@Mock private NotificationAssistants mAssistants;
@@ -242,8 +245,8 @@
}
@Override
- void logSmartSuggestionsVisible(NotificationRecord r) {
- super.logSmartSuggestionsVisible(r);
+ void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) {
+ super.logSmartSuggestionsVisible(r, notificationLocation);
countLogSmartSuggestionsVisible++;
}
@@ -2528,11 +2531,13 @@
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addNotification(r);
- mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, true);
+ mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, true,
+ NOTIFICATION_LOCATION_UNKNOWN);
verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(true), eq((true)));
assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
- mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, false);
+ mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, false,
+ NOTIFICATION_LOCATION_UNKNOWN);
verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(true), eq((false)));
assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
}
@@ -2542,11 +2547,13 @@
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addNotification(r);
- mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true);
+ mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true,
+ NOTIFICATION_LOCATION_UNKNOWN);
assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(false), eq((true)));
- mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, false);
+ mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, false,
+ NOTIFICATION_LOCATION_UNKNOWN);
assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
verify(mAssistants).notifyAssistantExpansionChangedLocked(
eq(r.sbn), eq(false), eq((false)));
@@ -3793,7 +3800,8 @@
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addNotification(r);
- mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true);
+ mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true,
+ NOTIFICATION_LOCATION_UNKNOWN);
NotificationVisibility[] notificationVisibility = new NotificationVisibility[] {
NotificationVisibility.obtain(r.getKey(), 0, 0, true)
};
@@ -3808,7 +3816,8 @@
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addNotification(r);
- mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true);
+ mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true,
+ NOTIFICATION_LOCATION_UNKNOWN);
assertEquals(0, mService.countLogSmartSuggestionsVisible);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 319ffed..8be63fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -31,7 +31,6 @@
import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
-import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING;
@@ -76,9 +75,6 @@
mStack = (TestActivityStack) new StackBuilder(mRootActivityContainer).build();
mTask = mStack.getChildAt(0);
mActivity = mTask.getTopActivity();
-
- doReturn(false).when(mService).isBooting();
- doReturn(true).when(mService).isBooted();
}
@Test
@@ -121,23 +117,22 @@
mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
- // The activity is in the focused stack so it should be resumed.
+ // The activity is in the focused stack so it should not move to paused.
mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
- assertTrue(mActivity.isState(RESUMED));
+ assertTrue(mActivity.isState(STOPPED));
assertFalse(pauseFound.value);
- // Make the activity non focusable
- mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
- doReturn(false).when(mActivity).isFocusable();
+ // Clear focused stack
+ final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ when(display.getFocusedStack()).thenReturn(null);
- // If the activity is not focusable, it should move to paused.
+ // In the unfocused stack, the activity should move to paused.
mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
assertTrue(mActivity.isState(PAUSING));
assertTrue(pauseFound.value);
// Make sure that the state does not change for current non-stopping states.
mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped");
- doReturn(true).when(mActivity).isFocusable();
mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index ea8f33f..68df87e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -55,7 +55,6 @@
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
import android.os.Looper;
-import android.os.PowerManager;
import android.os.Process;
import android.os.UserHandle;
import android.service.voice.IVoiceInteractionSession;
@@ -426,7 +425,6 @@
doReturn(mock(IPackageManager.class)).when(this).getPackageManager();
// allow background activity starts by default
doReturn(true).when(this).isBackgroundActivityStartsEnabled();
- doNothing().when(this).updateCpuStats();
}
void setup(IntentFirewall intentFirewall, PendingIntentController intentController,
@@ -582,8 +580,6 @@
doNothing().when(this).acquireLaunchWakelock();
doReturn(mKeyguardController).when(this).getKeyguardController();
- mLaunchingActivity = mock(PowerManager.WakeLock.class);
-
initialize();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
index d0b9225..ea5ab7b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
@@ -70,9 +70,9 @@
mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
verify(mTransaction).reparent(eq(mToken.getSurfaceControl()),
- eq(mToken.mSurfaceAnimator.mLeash.getHandle()));
+ eq(mToken.mSurfaceAnimator.mLeash));
verify(mTransaction).reparent(eq(mToken.mSurfaceAnimator.mLeash),
- eq(mToken.mAnimationBoundsLayer.getHandle()));
+ eq(mToken.mAnimationBoundsLayer));
}
@Test
@@ -111,7 +111,7 @@
mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
verify(mTransaction).reparent(eq(mToken.getSurfaceControl()),
- eq(mToken.mSurfaceAnimator.mLeash.getHandle()));
+ eq(mToken.mSurfaceAnimator.mLeash));
assertThat(mToken.mAnimationBoundsLayer).isNull();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
index 1c5391e..9ce5795 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -106,7 +106,7 @@
}
public void notifyTransitionStarting(int transit) {
- mListener.onAppTransitionStartingLocked(transit, null, null, 0, 0, 0);
+ mListener.onAppTransitionStartingLocked(transit, 0, 0, 0);
}
public void notifyTransitionFinished() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 413b6f4..9478be9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -25,6 +25,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.eq;
import android.graphics.Point;
@@ -43,6 +44,7 @@
import com.android.server.testutils.OffsettableClock;
import com.android.server.testutils.TestHandler;
+import com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import org.junit.Before;
@@ -60,8 +62,10 @@
public class RemoteAnimationControllerTest extends WindowTestsBase {
@Mock SurfaceControl mMockLeash;
+ @Mock SurfaceControl mMockThumbnailLeash;
@Mock Transaction mMockTransaction;
@Mock OnAnimationFinishedCallback mFinishedCallback;
+ @Mock OnAnimationFinishedCallback mThumbnailFinishedCallback;
@Mock IRemoteAnimationRunner mMockRunner;
private RemoteAnimationAdapter mAdapter;
private RemoteAnimationController mController;
@@ -84,8 +88,8 @@
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
mDisplayContent.mOpeningApps.add(win.mAppToken);
try {
- final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+ new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
mController.goodToGo();
mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
@@ -117,8 +121,8 @@
@Test
public void testCancel() throws Exception {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
- final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+ new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
mController.goodToGo();
@@ -129,8 +133,8 @@
@Test
public void testTimeout() throws Exception {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
- final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+ new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
mController.goodToGo();
@@ -147,8 +151,8 @@
mWm.setAnimationScale(2, 5.0f);
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
"testWin");
- final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win.mAppToken, new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
mController.goodToGo();
@@ -176,8 +180,8 @@
@Test
public void testNotReallyStarted() {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
- mController.createAnimationAdapter(win.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
+ mController.createRemoteAnimationRecord(win.mAppToken,
+ new Point(50, 100), new Rect(50, 100, 150, 150), null);
mController.goodToGo();
verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
}
@@ -186,10 +190,10 @@
public void testOneNotStarted() throws Exception {
final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1");
final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2");
- mController.createAnimationAdapter(win1.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
- final AnimationAdapter adapter = mController.createAnimationAdapter(win2.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
+ mController.createRemoteAnimationRecord(win1.mAppToken,
+ new Point(50, 100), new Rect(50, 100, 150, 150), null);
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win2.mAppToken,
+ new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
mController.goodToGo();
mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
@@ -205,8 +209,8 @@
@Test
public void testRemovedBeforeStarted() {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
- final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+ new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
win.mAppToken.removeImmediately();
mController.goodToGo();
@@ -214,6 +218,49 @@
verify(mFinishedCallback).onAnimationFinished(eq(adapter));
}
+ @Test
+ public void testChange() throws Exception {
+ final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ mDisplayContent.mChangingApps.add(win.mAppToken);
+ try {
+ final RemoteAnimationRecord record = mController.createRemoteAnimationRecord(
+ win.mAppToken, new Point(50, 100), new Rect(50, 100, 150, 150),
+ new Rect(0, 0, 200, 200));
+ assertNotNull(record.mThumbnailAdapter);
+ ((AnimationAdapter) record.mAdapter)
+ .startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+ ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
+ mMockTransaction, mThumbnailFinishedCallback);
+ mController.goodToGo();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+ final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+ ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+ verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
+ assertEquals(1, appsCaptor.getValue().length);
+ final RemoteAnimationTarget app = appsCaptor.getValue()[0];
+ assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode);
+ assertEquals(new Point(50, 100), app.position);
+ assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds);
+ assertEquals(new Rect(0, 0, 200, 200), app.startBounds);
+ assertEquals(mMockLeash, app.leash);
+ assertEquals(mMockThumbnailLeash, app.startLeash);
+ assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect);
+ assertEquals(false, app.isTranslucent);
+ verify(mMockTransaction).setLayer(mMockLeash, app.prefixOrderIndex);
+ verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y);
+ verify(mMockTransaction).setWindowCrop(mMockLeash, new Rect(0, 0, 200, 200));
+ verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0);
+
+ finishedCaptor.getValue().onAnimationFinished();
+ verify(mFinishedCallback).onAnimationFinished(eq(record.mAdapter));
+ verify(mThumbnailFinishedCallback).onAnimationFinished(eq(record.mThumbnailAdapter));
+ } finally {
+ mDisplayContent.mChangingApps.clear();
+ }
+ }
+
private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
verify(binder, atLeast(0)).asBinder();
verifyNoMoreInteractions(binder);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index ad80cd6..9b84215 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -90,7 +90,7 @@
final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
OnAnimationFinishedCallback.class);
assertAnimating(mAnimatable);
- verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash.getHandle()));
+ verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash));
verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
callbackCaptor.getValue().onAnimationFinished(mSpec);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
index b996bfb..c595868 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
@@ -19,6 +19,10 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.TaskPositioner.MIN_ASPECT;
import static com.android.server.wm.WindowManagerService.dipToPixel;
import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
@@ -38,13 +42,12 @@
import org.junit.Before;
import org.junit.Test;
-import org.mockito.Mockito;
/**
* Tests for the {@link TaskPositioner} class.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:TaskPositionerTests
+ * atest WmTests:TaskPositionerTests
*/
@SmallTest
public class TaskPositionerTests extends WindowTestsBase {
@@ -73,18 +76,17 @@
mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm);
mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm);
- mPositioner = new TaskPositioner(mWm, Mockito.mock(IActivityTaskManager.class));
+ mPositioner = new TaskPositioner(mWm, mock(IActivityTaskManager.class));
mPositioner.register(mDisplayContent);
- mWindow = Mockito.spy(createWindow(null, TYPE_BASE_APPLICATION, "window"));
- final Task task = Mockito.spy(mWindow.getTask());
- Mockito.when(mWindow.getTask()).thenReturn(task);
-
- Mockito.doAnswer(invocation -> {
+ mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
+ final Task task = mWindow.getTask();
+ spyOn(task);
+ doAnswer(invocation -> {
final Rect rect = (Rect) invocation.getArguments()[0];
rect.set(mDimBounds);
- return (Void) null;
- }).when(task).getDimBounds(Mockito.any(Rect.class));
+ return null;
+ }).when(task).getDimBounds(any(Rect.class));
mWindow.getStack().setWindowingMode(WINDOWING_MODE_FREEFORM);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 946ffb60..d29e3fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -53,7 +53,7 @@
public void setUp() {
final UserManager um = UserManager.get(getInstrumentation().getTargetContext());
mTestUserId = um.getUserHandle();
- mPersister = new TaskSnapshotPersister(userId -> FILES_DIR);
+ mPersister = new TaskSnapshotPersister(mWm, userId -> FILES_DIR);
mLoader = new TaskSnapshotLoader(mPersister);
mPersister.start();
}
diff --git a/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
index 2ed11fe..fa472e2 100644
--- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java
+++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
@@ -18,11 +18,14 @@
import android.annotation.UserIdInt;
import android.app.PendingIntent;
+import android.app.usage.UsageStatsManagerInternal;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
@@ -163,6 +166,9 @@
/** Map of observerId to details of the time limit group */
SparseArray<SessionUsageGroup> sessionUsageGroups = new SparseArray<>();
+ /** Map of observerId to details of the app usage limit group */
+ SparseArray<AppUsageLimitGroup> appUsageLimitGroups = new SparseArray<>();
+
private ObserverAppData(int uid) {
this.uid = uid;
}
@@ -177,6 +183,10 @@
sessionUsageGroups.remove(observerId);
}
+ @GuardedBy("mLock")
+ void removeAppUsageLimitGroup(int observerId) {
+ appUsageLimitGroups.remove(observerId);
+ }
@GuardedBy("mLock")
void dump(PrintWriter pw) {
@@ -194,6 +204,12 @@
sessionUsageGroups.valueAt(i).dump(pw);
pw.println();
}
+ pw.println(" App Usage Limit Groups:");
+ final int nAppUsageLimitGroups = appUsageLimitGroups.size();
+ for (int i = 0; i < nAppUsageLimitGroups; i++) {
+ appUsageLimitGroups.valueAt(i).dump(pw);
+ pw.println();
+ }
}
}
@@ -493,6 +509,54 @@
}
}
+ class AppUsageLimitGroup extends UsageGroup {
+ private boolean mGroupLimit;
+
+ public AppUsageLimitGroup(UserData user, ObserverAppData observerApp, int observerId,
+ String[] observed, long timeLimitMs, PendingIntent limitReachedCallback) {
+ super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback);
+ mGroupLimit = observed.length > 1;
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void remove() {
+ super.remove();
+ ObserverAppData observerApp = mObserverAppRef.get();
+ if (observerApp != null) {
+ observerApp.removeAppUsageLimitGroup(mObserverId);
+ }
+ }
+
+ @GuardedBy("mLock")
+ boolean isGroupLimit() {
+ return mGroupLimit;
+ }
+
+ @GuardedBy("mLock")
+ long getTotaUsageLimit() {
+ return mTimeLimitMs;
+ }
+
+ @GuardedBy("mLock")
+ long getUsageRemaining() {
+ // If there is currently an active session, account for its usage
+ if (mActives > 0) {
+ return mTimeLimitMs - mUsageTimeMs - (getUptimeMillis() - mLastKnownUsageTimeMs);
+ } else {
+ return mTimeLimitMs - mUsageTimeMs;
+ }
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void dump(PrintWriter pw) {
+ super.dump(pw);
+ pw.print(" groupLimit=");
+ pw.print(mGroupLimit);
+ }
+ }
+
private class MyHandler extends Handler {
static final int MSG_CHECK_TIMEOUT = 1;
@@ -553,6 +617,12 @@
/** Overrideable for testing purposes */
@VisibleForTesting
+ protected long getAppUsageLimitObserverPerUidLimit() {
+ return MAX_OBSERVER_PER_UID;
+ }
+
+ /** Overrideable for testing purposes */
+ @VisibleForTesting
protected long getMinTimeLimit() {
return ONE_MINUTE;
}
@@ -572,6 +642,61 @@
}
}
+ @VisibleForTesting
+ AppUsageLimitGroup getAppUsageLimitGroup(int observerAppUid, int observerId) {
+ synchronized (mLock) {
+ return getOrCreateObserverAppDataLocked(observerAppUid).appUsageLimitGroups.get(
+ observerId);
+ }
+ }
+
+ /**
+ * Returns an object describing the app usage limit for the given package which was set via
+ * {@link #addAppUsageLimitObserver).
+ * If there are multiple limits that apply to the package, the one with the smallest
+ * time remaining will be returned.
+ */
+ public UsageStatsManagerInternal.AppUsageLimitData getAppUsageLimit(
+ String packageName, UserHandle user) {
+ synchronized (mLock) {
+ final UserData userData = getOrCreateUserDataLocked(user.getIdentifier());
+ if (userData == null) {
+ return null;
+ }
+
+ final ArrayList<UsageGroup> usageGroups = userData.observedMap.get(packageName);
+ if (usageGroups == null || usageGroups.isEmpty()) {
+ return null;
+ }
+
+ final ArraySet<AppUsageLimitGroup> usageLimitGroups = new ArraySet<>();
+ for (int i = 0; i < usageGroups.size(); i++) {
+ if (usageGroups.get(i) instanceof AppUsageLimitGroup) {
+ final AppUsageLimitGroup group = (AppUsageLimitGroup) usageGroups.get(i);
+ for (int j = 0; j < group.mObserved.length; j++) {
+ if (group.mObserved[j].equals(packageName)) {
+ usageLimitGroups.add(group);
+ break;
+ }
+ }
+ }
+ }
+ if (usageLimitGroups.isEmpty()) {
+ return null;
+ }
+
+ AppUsageLimitGroup smallestGroup = usageLimitGroups.valueAt(0);
+ for (int i = 1; i < usageLimitGroups.size(); i++) {
+ final AppUsageLimitGroup otherGroup = usageLimitGroups.valueAt(i);
+ if (otherGroup.getUsageRemaining() < smallestGroup.getUsageRemaining()) {
+ smallestGroup = otherGroup;
+ }
+ }
+ return new UsageStatsManagerInternal.AppUsageLimitData(smallestGroup.isGroupLimit(),
+ smallestGroup.getTotaUsageLimit(), smallestGroup.getUsageRemaining());
+ }
+ }
+
/** Returns an existing UserData object for the given userId, or creates one */
@GuardedBy("mLock")
private UserData getOrCreateUserDataLocked(int userId) {
@@ -726,6 +851,61 @@
}
/**
+ * Registers an app usage limit observer with the given details.
+ * Existing app usage limit observer with the same observerId will be removed.
+ */
+ public void addAppUsageLimitObserver(int requestingUid, int observerId, String[] observed,
+ long timeLimit, PendingIntent callbackIntent, @UserIdInt int userId) {
+ if (timeLimit < getMinTimeLimit()) {
+ throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit());
+ }
+ synchronized (mLock) {
+ UserData user = getOrCreateUserDataLocked(userId);
+ ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+ AppUsageLimitGroup group = observerApp.appUsageLimitGroups.get(observerId);
+ if (group != null) {
+ // Remove previous app usage group associated with observerId
+ group.remove();
+ }
+
+ final int observerIdCount = observerApp.appUsageLimitGroups.size();
+ if (observerIdCount >= getAppUsageLimitObserverPerUidLimit()) {
+ throw new IllegalStateException(
+ "Too many app usage observers added by uid " + requestingUid);
+ }
+ group = new AppUsageLimitGroup(user, observerApp, observerId, observed, timeLimit,
+ callbackIntent);
+ observerApp.appUsageLimitGroups.append(observerId, group);
+
+ if (DEBUG) {
+ Slog.d(TAG, "addObserver " + observed + " for " + timeLimit);
+ }
+
+ user.addUsageGroup(group);
+ noteActiveLocked(user, group, getUptimeMillis());
+ }
+ }
+
+ /**
+ * Remove a registered observer by observerId and calling uid.
+ *
+ * @param requestingUid The calling uid
+ * @param observerId The unique observer id for this user
+ * @param userId The user id of the observer
+ */
+ public void removeAppUsageLimitObserver(int requestingUid, int observerId,
+ @UserIdInt int userId) {
+ synchronized (mLock) {
+ final ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+ final AppUsageLimitGroup group = observerApp.appUsageLimitGroups.get(observerId);
+ if (group != null) {
+ // Remove previous app usage group associated with observerId
+ group.remove();
+ }
+ }
+ }
+
+ /**
* Called when an entity becomes active.
*
* @param name The entity that became active
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index 9a5bd13..f1ddfe4 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -218,6 +218,14 @@
case (int) IntervalStatsProto.Event.INSTANCE_ID:
event.mInstanceId = parser.readInt(IntervalStatsProto.Event.INSTANCE_ID);
break;
+ case (int) IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX:
+ event.mTaskRootPackage = getCachedStringRef(stringPool.get(
+ parser.readInt(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX) - 1));
+ break;
+ case (int) IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX:
+ event.mTaskRootClass = getCachedStringRef(stringPool.get(
+ parser.readInt(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX) - 1));
+ break;
case ProtoInputStream.NO_MORE_FIELDS:
// Handle default values for certain events types
switch (event.mEventType) {
@@ -332,6 +340,12 @@
if (event.mClass != null) {
event.mClass = getCachedStringRef(event.mClass);
}
+ if (event.mTaskRootPackage != null) {
+ event.mTaskRootPackage = getCachedStringRef(event.mTaskRootPackage);
+ }
+ if (event.mTaskRootClass != null) {
+ event.mTaskRootClass = getCachedStringRef(event.mTaskRootClass);
+ }
if (event.mEventType == NOTIFICATION_INTERRUPTION) {
event.mNotificationChannelId = getCachedStringRef(event.mNotificationChannelId);
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java
index d706537..11d49eb 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProto.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java
@@ -442,6 +442,28 @@
proto.write(IntervalStatsProto.Event.FLAGS, event.mFlags);
proto.write(IntervalStatsProto.Event.TYPE, event.mEventType);
proto.write(IntervalStatsProto.Event.INSTANCE_ID, event.mInstanceId);
+ if (event.mTaskRootPackage != null) {
+ final int taskRootPackageIndex = stats.mStringCache.indexOf(event.mTaskRootPackage);
+ if (taskRootPackageIndex >= 0) {
+ proto.write(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX,
+ taskRootPackageIndex + 1);
+ } else {
+ // Task root package not in Stringpool for some reason.
+ Slog.w(TAG, "Usage event task root package name (" + event.mTaskRootPackage
+ + ") not found in IntervalStats string cache");
+ }
+ }
+ if (event.mTaskRootClass != null) {
+ final int taskRootClassIndex = stats.mStringCache.indexOf(event.mTaskRootClass);
+ if (taskRootClassIndex >= 0) {
+ proto.write(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX,
+ taskRootClassIndex + 1);
+ } else {
+ // Task root class not in Stringpool for some reason.
+ Slog.w(TAG, "Usage event task root class name (" + event.mTaskRootClass
+ + ") not found in IntervalStats string cache");
+ }
+ }
switch (event.mEventType) {
case UsageEvents.Event.CONFIGURATION_CHANGE:
if (event.mConfiguration != null) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 76a3aa8..85939d4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -22,6 +22,8 @@
import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
+import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
+import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY;
import android.Manifest;
import android.app.ActivityManager;
@@ -39,6 +41,7 @@
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManager.StandbyBuckets;
+import android.app.usage.UsageStatsManager.UsageSource;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -65,6 +68,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
@@ -132,6 +136,7 @@
private File mUsageStatsDir;
long mRealTimeSnapshot;
long mSystemTimeSnapshot;
+ int mUsageSource;
/** Manages the standby state of apps. */
AppStandbyController mAppStandby;
@@ -258,6 +263,7 @@
} else {
Slog.w(TAG, "Missing procfs interface: " + KERNEL_COUNTER_FILE);
}
+ readUsageSourceSetting();
}
}
@@ -268,6 +274,13 @@
return mDpmInternal;
}
+ private void readUsageSourceSetting() {
+ synchronized (mLock) {
+ mUsageSource = Settings.Global.getInt(getContext().getContentResolver(),
+ Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE, USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+ }
+ }
+
private class UserActionsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@@ -459,12 +472,28 @@
service.reportEvent(event);
mAppStandby.reportEvent(event, elapsedRealtime, userId);
+
+ String packageName;
+
+ switch(mUsageSource) {
+ case USAGE_SOURCE_CURRENT_ACTIVITY:
+ packageName = event.getPackageName();
+ break;
+ case USAGE_SOURCE_TASK_ROOT_ACTIVITY:
+ default:
+ packageName = event.getTaskRootPackageName();
+ if (packageName == null) {
+ packageName = event.getPackageName();
+ }
+ break;
+ }
+
switch (event.mEventType) {
case Event.ACTIVITY_RESUMED:
synchronized (mVisibleActivities) {
mVisibleActivities.put(event.mInstanceId, event.getClassName());
try {
- mAppTimeLimit.noteUsageStart(event.getPackageName(), userId);
+ mAppTimeLimit.noteUsageStart(packageName, userId);
} catch (IllegalArgumentException iae) {
Slog.e(TAG, "Failed to note usage start", iae);
}
@@ -496,7 +525,7 @@
synchronized (mVisibleActivities) {
if (mVisibleActivities.removeReturnOld(event.mInstanceId) != null) {
try {
- mAppTimeLimit.noteUsageStop(event.getPackageName(), userId);
+ mAppTimeLimit.noteUsageStop(packageName, userId);
} catch (IllegalArgumentException iae) {
Slog.w(TAG, "Failed to note usage stop", iae);
}
@@ -638,7 +667,7 @@
* Called by the Binder stub.
*/
UsageEvents queryEventsForPackage(int userId, long beginTime, long endTime,
- String packageName) {
+ String packageName, boolean includeTaskRoot) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
if (!validRange(timeNow, beginTime, endTime)) {
@@ -647,7 +676,7 @@
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
- return service.queryEventsForPackage(beginTime, endTime, packageName);
+ return service.queryEventsForPackage(beginTime, endTime, packageName, includeTaskRoot);
}
}
@@ -738,6 +767,10 @@
mAppStandby.dumpState(args, pw);
}
+ idpw.println();
+ idpw.printPair("Usage Source", UsageStatsManager.usageSourceToString(mUsageSource));
+ idpw.println();
+
mAppTimeLimit.dump(null, pw);
}
}
@@ -808,7 +841,7 @@
return mode == AppOpsManager.MODE_ALLOWED;
}
- private boolean hasObserverPermission(String callingPackage) {
+ private boolean hasObserverPermission() {
final int callingUid = Binder.getCallingUid();
DevicePolicyManagerInternal dpmInternal = getDpmInternal();
if (callingUid == Process.SYSTEM_UID
@@ -822,6 +855,22 @@
== PackageManager.PERMISSION_GRANTED;
}
+ private boolean hasPermissions(String callingPackage, String... permissions) {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid == Process.SYSTEM_UID) {
+ // Caller is the system, so proceed.
+ return true;
+ }
+
+ boolean hasPermissions = true;
+ final Context context = getContext();
+ for (int i = 0; i < permissions.length; i++) {
+ hasPermissions = hasPermissions && (context.checkCallingPermission(permissions[i])
+ == PackageManager.PERMISSION_GRANTED);
+ }
+ return hasPermissions;
+ }
+
private void checkCallerIsSystemOrSameApp(String pkg) {
if (isCallingUidSystem()) {
return;
@@ -939,10 +988,12 @@
final int callingUserId = UserHandle.getUserId(callingUid);
checkCallerIsSameApp(callingPackage);
+ final boolean includeTaskRoot = hasPermission(callingPackage);
+
final long token = Binder.clearCallingIdentity();
try {
return UsageStatsService.this.queryEventsForPackage(callingUserId, beginTime,
- endTime, callingPackage);
+ endTime, callingPackage, includeTaskRoot);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -989,7 +1040,7 @@
final long token = Binder.clearCallingIdentity();
try {
return UsageStatsService.this.queryEventsForPackage(userId, beginTime,
- endTime, pkg);
+ endTime, pkg, true);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1229,7 +1280,7 @@
public void registerAppUsageObserver(int observerId,
String[] packages, long timeLimitMs, PendingIntent
callbackIntent, String callingPackage) {
- if (!hasObserverPermission(callingPackage)) {
+ if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
@@ -1252,7 +1303,7 @@
@Override
public void unregisterAppUsageObserver(int observerId, String callingPackage) {
- if (!hasObserverPermission(callingPackage)) {
+ if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
@@ -1271,7 +1322,7 @@
long timeLimitMs, long sessionThresholdTimeMs,
PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent,
String callingPackage) {
- if (!hasObserverPermission(callingPackage)) {
+ if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
@@ -1295,7 +1346,7 @@
@Override
public void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage) {
- if (!hasObserverPermission(callingPackage)) {
+ if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
@@ -1311,6 +1362,51 @@
}
@Override
+ public void registerAppUsageLimitObserver(int observerId, String[] packages,
+ long timeLimitMs, PendingIntent callbackIntent, String callingPackage) {
+ if (!hasPermissions(callingPackage,
+ Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) {
+ throw new SecurityException("Caller doesn't have both SUSPEND_APPS and "
+ + "OBSERVE_APP_USAGE permissions");
+ }
+
+ if (packages == null || packages.length == 0) {
+ throw new IllegalArgumentException("Must specify at least one package");
+ }
+ if (callbackIntent == null) {
+ throw new NullPointerException("callbackIntent can't be null");
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UsageStatsService.this.registerAppUsageLimitObserver(callingUid, observerId,
+ packages, timeLimitMs, callbackIntent, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void unregisterAppUsageLimitObserver(int observerId, String callingPackage) {
+ if (!hasPermissions(callingPackage,
+ Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) {
+ throw new SecurityException("Caller doesn't have both SUSPEND_APPS and "
+ + "OBSERVE_APP_USAGE permissions");
+ }
+
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UsageStatsService.this.unregisterAppUsageLimitObserver(
+ callingUid, observerId, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void reportUsageStart(IBinder activity, String token, String callingPackage) {
reportPastUsageStart(activity, token, 0, callingPackage);
}
@@ -1373,6 +1469,21 @@
Binder.restoreCallingIdentity(binderToken);
}
}
+
+ @Override
+ public @UsageSource int getUsageSource() {
+ if (!hasObserverPermission()) {
+ throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
+ }
+ synchronized (mLock) {
+ return mUsageSource;
+ }
+ }
+
+ @Override
+ public void forceUsageSourceSettingRead() {
+ readUsageSourceSetting();
+ }
}
void registerAppUsageObserver(int callingUid, int observerId, String[] packages,
@@ -1397,6 +1508,16 @@
mAppTimeLimit.removeUsageSessionObserver(callingUid, sessionObserverId, userId);
}
+ void registerAppUsageLimitObserver(int callingUid, int observerId, String[] packages,
+ long timeLimitMs, PendingIntent callbackIntent, int userId) {
+ mAppTimeLimit.addAppUsageLimitObserver(callingUid, observerId, packages, timeLimitMs,
+ callbackIntent, userId);
+ }
+
+ void unregisterAppUsageLimitObserver(int callingUid, int observerId, int userId) {
+ mAppTimeLimit.removeAppUsageLimitObserver(callingUid, observerId, userId);
+ }
+
/**
* This local service implementation is primarily used by ActivityManagerService.
* ActivityManagerService will call these methods holding the 'am' lock, which means we
@@ -1406,7 +1527,7 @@
@Override
public void reportEvent(ComponentName component, int userId, int eventType,
- int instanceId) {
+ int instanceId, ComponentName taskRoot) {
if (component == null) {
Slog.w(TAG, "Event reported without a component name");
return;
@@ -1416,6 +1537,13 @@
event.mPackage = component.getPackageName();
event.mClass = component.getClassName();
event.mInstanceId = instanceId;
+ if (taskRoot == null) {
+ event.mTaskRootPackage = null;
+ event.mTaskRootClass = null;
+ } else {
+ event.mTaskRootPackage = taskRoot.getPackageName();
+ event.mTaskRootClass = taskRoot.getClassName();
+ }
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@@ -1595,5 +1723,10 @@
public void reportExemptedSyncStart(String packageName, int userId) {
mAppStandby.postReportExemptedSyncStart(packageName, userId);
}
+
+ @Override
+ public AppUsageLimitData getAppUsageLimit(String packageName, UserHandle user) {
+ return mAppTimeLimit.getAppUsageLimit(packageName, user);
+ }
}
}
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 2d1098c7..d52d32f 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -401,7 +401,7 @@
}
UsageEvents queryEvents(final long beginTime, final long endTime,
- boolean obfuscateInstantApps) {
+ boolean obfuscateInstantApps) {
final ArraySet<String> names = new ArraySet<>();
List<Event> results = queryStats(INTERVAL_DAILY,
beginTime, endTime, new StatCombiner<Event>() {
@@ -425,6 +425,12 @@
if (event.mClass != null) {
names.add(event.mClass);
}
+ if (event.mTaskRootPackage != null) {
+ names.add(event.mTaskRootPackage);
+ }
+ if (event.mTaskRootClass != null) {
+ names.add(event.mTaskRootClass);
+ }
accumulatedResult.add(event);
}
}
@@ -436,11 +442,11 @@
String[] table = names.toArray(new String[names.size()]);
Arrays.sort(table);
- return new UsageEvents(results, table);
+ return new UsageEvents(results, table, true);
}
UsageEvents queryEventsForPackage(final long beginTime, final long endTime,
- final String packageName) {
+ final String packageName, boolean includeTaskRoot) {
final ArraySet<String> names = new ArraySet<>();
names.add(packageName);
final List<Event> results = queryStats(INTERVAL_DAILY,
@@ -459,6 +465,12 @@
if (event.mClass != null) {
names.add(event.mClass);
}
+ if (includeTaskRoot && event.mTaskRootPackage != null) {
+ names.add(event.mTaskRootPackage);
+ }
+ if (includeTaskRoot && event.mTaskRootClass != null) {
+ names.add(event.mTaskRootClass);
+ }
accumulatedResult.add(event);
}
});
@@ -469,7 +481,7 @@
final String[] table = names.toArray(new String[names.size()]);
Arrays.sort(table);
- return new UsageEvents(results, table);
+ return new UsageEvents(results, table, includeTaskRoot);
}
void persistActiveStats() {
@@ -684,6 +696,14 @@
pw.printPair("instanceId", event.getInstanceId());
}
+ if (event.getTaskRootPackageName() != null) {
+ pw.printPair("taskRootPackage", event.getTaskRootPackageName());
+ }
+
+ if (event.getTaskRootClassName() != null) {
+ pw.printPair("taskRootClass", event.getTaskRootClassName());
+ }
+
if (event.mNotificationChannelId != null) {
pw.printPair("channelId", event.mNotificationChannelId);
}
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index feb7b76..20855b7 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -10,6 +10,7 @@
static_libs: [
"android.hardware.usb-V1.0-java",
"android.hardware.usb-V1.1-java",
+ "android.hardware.usb-V1.2-java",
"android.hardware.usb.gadget-V1.0-java",
],
}
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 6f210e3..50e4faa 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -16,6 +16,8 @@
package com.android.server.usb;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST;
import static android.hardware.usb.UsbPortStatus.MODE_DFP;
@@ -29,19 +31,23 @@
import android.Manifest;
import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
import android.hardware.usb.ParcelableUsbPort;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
-import android.hardware.usb.V1_0.IUsb;
import android.hardware.usb.V1_0.PortRole;
import android.hardware.usb.V1_0.PortRoleType;
-import android.hardware.usb.V1_0.PortStatus;
import android.hardware.usb.V1_0.Status;
-import android.hardware.usb.V1_1.IUsbCallback;
import android.hardware.usb.V1_1.PortStatus_1_1;
+import android.hardware.usb.V1_2.IUsb;
+import android.hardware.usb.V1_2.IUsbCallback;
+import android.hardware.usb.V1_2.PortStatus;
import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
import android.os.Bundle;
@@ -55,18 +61,20 @@
import android.os.UserHandle;
import android.service.usb.UsbPortInfoProto;
import android.service.usb.UsbPortManagerProto;
+import android.service.usb.UsbServiceProto;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.util.StatsLog;
import com.android.internal.annotations.GuardedBy;
+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.dump.DualDumpOutputStream;
import com.android.server.FgThread;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.NoSuchElementException;
/**
@@ -85,6 +93,7 @@
private static final String TAG = "UsbPortManager";
private static final int MSG_UPDATE_PORTS = 1;
+ private static final int MSG_SYSTEM_READY = 2;
// All non-trivial role combinations.
private static final int COMBO_SOURCE_HOST =
@@ -132,7 +141,19 @@
// Maintains the current connected status of the port.
// Uploads logs only when the connection status is changes.
- private final HashMap<String, Boolean> mConnected = new HashMap<>();
+ private final ArrayMap<String, Boolean> mConnected = new ArrayMap<>();
+
+ // Maintains the USB contaminant status that was previously logged.
+ // Logs get uploaded only when contaminant presence status changes.
+ private final ArrayMap<String, Integer> mContaminantStatus = new ArrayMap<>();
+
+ private NotificationManager mNotificationManager;
+
+ /**
+ * If there currently is a notification about contaminated USB port shown the id of the
+ * notification, or 0 if there is none.
+ */
+ private int mIsPortContaminatedNotificationId;
public UsbPortManager(Context context) {
mContext = context;
@@ -164,6 +185,90 @@
"ServiceStart: Failed to query port status", e);
}
}
+ mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
+ }
+
+ private void updateContaminantNotification() {
+ PortInfo currentPortInfo = null;
+ Resources r = mContext.getResources();
+
+ // Not handling multiple ports here. Showing the notification
+ // for the first port that returns CONTAMINANT_PRESENCE_DETECTED.
+ for (PortInfo portInfo : mPorts.values()) {
+ if (portInfo.mUsbPortStatus.getContaminantDetectionStatus()
+ == UsbPortStatus.CONTAMINANT_DETECTION_DETECTED) {
+ currentPortInfo = portInfo;
+ break;
+ }
+ }
+
+ if (currentPortInfo != null && mIsPortContaminatedNotificationId
+ != SystemMessage.NOTE_USB_CONTAMINANT_DETECTED) {
+ if (mIsPortContaminatedNotificationId
+ == SystemMessage.NOTE_USB_CONTAMINANT_NOT_DETECTED) {
+ mNotificationManager.cancelAsUser(null, mIsPortContaminatedNotificationId,
+ UserHandle.ALL);
+ }
+
+ mIsPortContaminatedNotificationId = SystemMessage.NOTE_USB_CONTAMINANT_DETECTED;
+ int titleRes = com.android.internal.R.string.usb_contaminant_detected_title;
+ CharSequence title = r.getText(titleRes);
+ String channel = SystemNotificationChannels.ALERTS;
+ CharSequence message = r.getText(
+ com.android.internal.R.string.usb_contaminant_detected_message);
+
+ Intent intent = new Intent();
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClassName("com.android.systemui",
+ "com.android.systemui.usb.UsbContaminantActivity");
+ intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(currentPortInfo.mUsbPort));
+
+ PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
+ intent, 0, null, UserHandle.CURRENT);
+
+ Notification.Builder builder = new Notification.Builder(mContext, channel)
+ .setOngoing(true)
+ .setTicker(title)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color
+ .system_notification_accent_color))
+ .setContentIntent(pi)
+ .setContentTitle(title)
+ .setContentText(message)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setSmallIcon(android.R.drawable.stat_sys_warning)
+ .setStyle(new Notification.BigTextStyle()
+ .bigText(message));
+ Notification notification = builder.build();
+ mNotificationManager.notifyAsUser(null, mIsPortContaminatedNotificationId, notification,
+ UserHandle.ALL);
+ } else if (currentPortInfo == null && mIsPortContaminatedNotificationId
+ == SystemMessage.NOTE_USB_CONTAMINANT_DETECTED) {
+ mNotificationManager.cancelAsUser(null, mIsPortContaminatedNotificationId,
+ UserHandle.ALL);
+
+ mIsPortContaminatedNotificationId = SystemMessage.NOTE_USB_CONTAMINANT_NOT_DETECTED;
+ int titleRes = com.android.internal.R.string.usb_contaminant_not_detected_title;
+ CharSequence title = r.getText(titleRes);
+ String channel = SystemNotificationChannels.ALERTS;
+ CharSequence message = r.getText(
+ com.android.internal.R.string.usb_contaminant_not_detected_message);
+
+ Notification.Builder builder = new Notification.Builder(mContext, channel)
+ .setSmallIcon(com.android.internal.R.drawable.ic_usb_48dp)
+ .setTicker(title)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color
+ .system_notification_accent_color))
+ .setContentTitle(title)
+ .setContentText(message)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setStyle(new Notification.BigTextStyle()
+ .bigText(message));
+ Notification notification = builder.build();
+ mNotificationManager.notifyAsUser(null, mIsPortContaminatedNotificationId, notification,
+ UserHandle.ALL);
+ }
}
public UsbPort[] getPorts() {
@@ -184,6 +289,43 @@
}
}
+ /**
+ * Enables/disables contaminant detection.
+ *
+ * @param portId port identifier.
+ * @param enable enable contaminant detection when set to true.
+ */
+ public void enableContaminantDetection(@NonNull String portId, boolean enable,
+ @NonNull IndentingPrintWriter pw) {
+ final PortInfo portInfo = mPorts.get(portId);
+ if (portInfo == null) {
+ if (pw != null) {
+ pw.println("No such USB port: " + portId);
+ }
+ return;
+ }
+
+ if (!portInfo.mUsbPort.supportsEnableContaminantPresenceDetection()) {
+ return;
+ }
+
+ if ((enable && portInfo.mUsbPortStatus.getContaminantDetectionStatus()
+ != UsbPortStatus.CONTAMINANT_DETECTION_DISABLED) || (!enable
+ && portInfo.mUsbPortStatus.getContaminantDetectionStatus()
+ == UsbPortStatus.CONTAMINANT_DETECTION_DISABLED)
+ || (portInfo.mUsbPortStatus.getContaminantDetectionStatus()
+ == UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED)) {
+ return;
+ }
+
+ try {
+ // Oneway call into the hal
+ mProxy.enableContaminantPresenceDetection(portId, enable);
+ } catch (RemoteException e) {
+ logAndPrintException(null, "Failed to set contaminant detection", e);
+ }
+ }
+
public void setPortRoles(String portId, int newPowerRole, int newDataRole,
IndentingPrintWriter pw) {
synchronized (mLock) {
@@ -371,6 +513,27 @@
}
}
+ /**
+ * Sets contaminant status for simulated USB port objects.
+ */
+ public void simulateContaminantStatus(String portId, boolean detected,
+ IndentingPrintWriter pw) {
+ synchronized (mLock) {
+ final RawPortInfo portInfo = mSimulatedPorts.get(portId);
+ if (portInfo == null) {
+ pw.println("Simulated port not found.");
+ return;
+ }
+
+ pw.println("Simulating wet port: portId=" + portId
+ + ", wet=" + detected);
+ portInfo.contaminantDetectionStatus = detected
+ ? UsbPortStatus.CONTAMINANT_DETECTION_DETECTED
+ : UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED;
+ updatePortsLocked(pw, null);
+ }
+ }
+
public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) {
synchronized (mLock) {
final RawPortInfo portInfo = mSimulatedPorts.get(portId);
@@ -441,7 +604,8 @@
this.portManager = portManager;
}
- public void notifyPortStatusChange(ArrayList<PortStatus> currentPortStatus, int retval) {
+ public void notifyPortStatusChange(
+ ArrayList<android.hardware.usb.V1_0.PortStatus> currentPortStatus, int retval) {
if (!portManager.mSystemReady) {
return;
}
@@ -453,14 +617,17 @@
ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
- for (PortStatus current : currentPortStatus) {
+ for (android.hardware.usb.V1_0.PortStatus current : currentPortStatus) {
RawPortInfo temp = new RawPortInfo(current.portName,
- current.supportedModes, current.currentMode,
+ current.supportedModes, CONTAMINANT_PROTECTION_NONE,
+ current.currentMode,
current.canChangeMode, current.currentPowerRole,
current.canChangePowerRole,
- current.currentDataRole, current.canChangeDataRole);
+ current.currentDataRole, current.canChangeDataRole,
+ false, CONTAMINANT_PROTECTION_NONE,
+ false, CONTAMINANT_DETECTION_NOT_SUPPORTED);
newPortInfo.add(temp);
- logAndPrint(Log.INFO, pw, "ClientCallback: " + current.portName);
+ logAndPrint(Log.INFO, pw, "ClientCallback V1_0: " + current.portName);
}
Message message = portManager.mHandler.obtainMessage();
@@ -485,14 +652,61 @@
ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
- for (PortStatus_1_1 current : currentPortStatus) {
+ int numStatus = currentPortStatus.size();
+ for (int i = 0; i < numStatus; i++) {
+ PortStatus_1_1 current = currentPortStatus.get(i);
RawPortInfo temp = new RawPortInfo(current.status.portName,
- current.supportedModes, current.currentMode,
+ current.supportedModes, CONTAMINANT_PROTECTION_NONE,
+ current.currentMode,
current.status.canChangeMode, current.status.currentPowerRole,
current.status.canChangePowerRole,
- current.status.currentDataRole, current.status.canChangeDataRole);
+ current.status.currentDataRole, current.status.canChangeDataRole,
+ false, CONTAMINANT_PROTECTION_NONE,
+ false, CONTAMINANT_DETECTION_NOT_SUPPORTED);
newPortInfo.add(temp);
- logAndPrint(Log.INFO, pw, "ClientCallback: " + current.status.portName);
+ logAndPrint(Log.INFO, pw, "ClientCallback V1_1: " + current.status.portName);
+ }
+
+ Message message = portManager.mHandler.obtainMessage();
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(PORT_INFO, newPortInfo);
+ message.what = MSG_UPDATE_PORTS;
+ message.setData(bundle);
+ portManager.mHandler.sendMessage(message);
+ }
+
+ public void notifyPortStatusChange_1_2(
+ ArrayList<PortStatus> currentPortStatus, int retval) {
+ if (!portManager.mSystemReady) {
+ return;
+ }
+
+ if (retval != Status.SUCCESS) {
+ logAndPrint(Log.ERROR, pw, "port status enquiry failed");
+ return;
+ }
+
+ ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
+
+ int numStatus = currentPortStatus.size();
+ for (int i = 0; i < numStatus; i++) {
+ PortStatus current = currentPortStatus.get(i);
+ RawPortInfo temp = new RawPortInfo(current.status_1_1.status.portName,
+ current.status_1_1.supportedModes,
+ current.supportedContaminantProtectionModes,
+ current.status_1_1.currentMode,
+ current.status_1_1.status.canChangeMode,
+ current.status_1_1.status.currentPowerRole,
+ current.status_1_1.status.canChangePowerRole,
+ current.status_1_1.status.currentDataRole,
+ current.status_1_1.status.canChangeDataRole,
+ current.supportsEnableContaminantPresenceProtection,
+ current.contaminantProtectionStatus,
+ current.supportsEnableContaminantPresenceDetection,
+ current.contaminantDetectionStatus);
+ newPortInfo.add(temp);
+ logAndPrint(Log.INFO, pw, "ClientCallback V1_2: "
+ + current.status_1_1.status.portName);
}
Message message = portManager.mHandler.obtainMessage();
@@ -573,16 +787,26 @@
for (int i = 0; i < count; i++) {
final RawPortInfo portInfo = mSimulatedPorts.valueAt(i);
addOrUpdatePortLocked(portInfo.portId, portInfo.supportedModes,
+ portInfo.supportedContaminantProtectionModes,
portInfo.currentMode, portInfo.canChangeMode,
portInfo.currentPowerRole, portInfo.canChangePowerRole,
- portInfo.currentDataRole, portInfo.canChangeDataRole, pw);
+ portInfo.currentDataRole, portInfo.canChangeDataRole,
+ portInfo.supportsEnableContaminantPresenceProtection,
+ portInfo.contaminantProtectionStatus,
+ portInfo.supportsEnableContaminantPresenceDetection,
+ portInfo.contaminantDetectionStatus, pw);
}
} else {
for (RawPortInfo currentPortInfo : newPortInfo) {
addOrUpdatePortLocked(currentPortInfo.portId, currentPortInfo.supportedModes,
+ currentPortInfo.supportedContaminantProtectionModes,
currentPortInfo.currentMode, currentPortInfo.canChangeMode,
currentPortInfo.currentPowerRole, currentPortInfo.canChangePowerRole,
- currentPortInfo.currentDataRole, currentPortInfo.canChangeDataRole, pw);
+ currentPortInfo.currentDataRole, currentPortInfo.canChangeDataRole,
+ currentPortInfo.supportsEnableContaminantPresenceProtection,
+ currentPortInfo.contaminantProtectionStatus,
+ currentPortInfo.supportsEnableContaminantPresenceDetection,
+ currentPortInfo.contaminantDetectionStatus, pw);
}
}
@@ -608,12 +832,16 @@
}
}
-
// Must only be called by updatePortsLocked.
private void addOrUpdatePortLocked(String portId, int supportedModes,
+ int supportedContaminantProtectionModes,
int currentMode, boolean canChangeMode,
int currentPowerRole, boolean canChangePowerRole,
int currentDataRole, boolean canChangeDataRole,
+ boolean supportsEnableContaminantPresenceProtection,
+ int contaminantProtectionStatus,
+ boolean supportsEnableContaminantPresenceDetection,
+ int contaminantDetectionStatus,
IndentingPrintWriter pw) {
// Only allow mode switch capability for dual role ports.
// Validate that the current mode matches the supported modes we expect.
@@ -664,12 +892,15 @@
// Update the port data structures.
PortInfo portInfo = mPorts.get(portId);
if (portInfo == null) {
- portInfo = new PortInfo(mContext.getSystemService(UsbManager.class), portId,
- supportedModes);
+ portInfo = new PortInfo(mContext.getSystemService(UsbManager.class),
+ portId, supportedModes, supportedContaminantProtectionModes,
+ supportsEnableContaminantPresenceProtection,
+ supportsEnableContaminantPresenceDetection);
portInfo.setStatus(currentMode, canChangeMode,
currentPowerRole, canChangePowerRole,
currentDataRole, canChangeDataRole,
- supportedRoleCombinations);
+ supportedRoleCombinations, contaminantProtectionStatus,
+ contaminantDetectionStatus);
mPorts.put(portId, portInfo);
} else {
// Sanity check that ports aren't changing definition out from under us.
@@ -681,10 +912,32 @@
+ ", current=" + UsbPort.modeToString(supportedModes));
}
+ if (supportsEnableContaminantPresenceProtection
+ != portInfo.mUsbPort.supportsEnableContaminantPresenceProtection()) {
+ logAndPrint(Log.WARN, pw,
+ "Ignoring inconsistent supportsEnableContaminantPresenceProtection"
+ + "USB port driver (should be immutable): "
+ + "previous="
+ + portInfo.mUsbPort.supportsEnableContaminantPresenceProtection()
+ + ", current=" + supportsEnableContaminantPresenceProtection);
+ }
+
+ if (supportsEnableContaminantPresenceDetection
+ != portInfo.mUsbPort.supportsEnableContaminantPresenceDetection()) {
+ logAndPrint(Log.WARN, pw,
+ "Ignoring inconsistent supportsEnableContaminantPresenceDetection "
+ + "USB port driver (should be immutable): "
+ + "previous="
+ + portInfo.mUsbPort.supportsEnableContaminantPresenceDetection()
+ + ", current=" + supportsEnableContaminantPresenceDetection);
+ }
+
+
if (portInfo.setStatus(currentMode, canChangeMode,
currentPowerRole, canChangePowerRole,
currentDataRole, canChangeDataRole,
- supportedRoleCombinations)) {
+ supportedRoleCombinations, contaminantProtectionStatus,
+ contaminantDetectionStatus)) {
portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED;
} else {
portInfo.mDisposition = PortInfo.DISPOSITION_READY;
@@ -695,16 +948,37 @@
private void handlePortAddedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
logAndPrint(Log.INFO, pw, "USB port added: " + portInfo);
sendPortChangedBroadcastLocked(portInfo);
+ updateContaminantNotification();
}
private void handlePortChangedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
logAndPrint(Log.INFO, pw, "USB port changed: " + portInfo);
sendPortChangedBroadcastLocked(portInfo);
+ updateContaminantNotification();
}
private void handlePortRemovedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
logAndPrint(Log.INFO, pw, "USB port removed: " + portInfo);
sendPortChangedBroadcastLocked(portInfo);
+ updateContaminantNotification();
+ }
+
+ // Constants have to be converted between USB HAL V1.2 ContaminantDetectionStatus
+ // to usb.proto as proto guidelines recommends 0 to be UNKNOWN/UNSUPPORTTED
+ // whereas HAL policy is against a loosely defined constant.
+ private static int convertContaminantDetectionStatusToProto(int contaminantDetectionStatus) {
+ switch (contaminantDetectionStatus) {
+ case UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED:
+ return UsbServiceProto.CONTAMINANT_STATUS_NOT_SUPPORTED;
+ case UsbPortStatus.CONTAMINANT_DETECTION_DISABLED:
+ return UsbServiceProto.CONTAMINANT_STATUS_DISABLED;
+ case UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED:
+ return UsbServiceProto.CONTAMINANT_STATUS_NOT_DETECTED;
+ case UsbPortStatus.CONTAMINANT_DETECTION_DETECTED:
+ return UsbServiceProto.CONTAMINANT_STATUS_DETECTED;
+ default:
+ return UsbServiceProto.CONTAMINANT_STATUS_UNKNOWN;
+ }
}
private void sendPortChangedBroadcastLocked(PortInfo portInfo) {
@@ -721,6 +995,33 @@
Manifest.permission.MANAGE_USB));
// Log to statsd
+
+ // Port is removed
+ if (portInfo.mUsbPortStatus == null) {
+ if (mConnected.containsKey(portInfo.mUsbPort.getId())) {
+ //Previous logged a connected. Set it to disconnected.
+ if (mConnected.get(portInfo.mUsbPort.getId())) {
+ StatsLog.write(StatsLog.USB_CONNECTOR_STATE_CHANGED,
+ StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED,
+ portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis);
+ }
+ mConnected.remove(portInfo.mUsbPort.getId());
+ }
+
+ if (mContaminantStatus.containsKey(portInfo.mUsbPort.getId())) {
+ //Previous logged a contaminant detected. Set it to not detected.
+ if ((mContaminantStatus.get(portInfo.mUsbPort.getId())
+ == UsbPortStatus.CONTAMINANT_DETECTION_DETECTED)) {
+ StatsLog.write(StatsLog.USB_CONTAMINANT_REPORTED,
+ portInfo.mUsbPort.getId(),
+ convertContaminantDetectionStatusToProto(
+ UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED));
+ }
+ mContaminantStatus.remove(portInfo.mUsbPort.getId());
+ }
+ return;
+ }
+
if (!mConnected.containsKey(portInfo.mUsbPort.getId())
|| (mConnected.get(portInfo.mUsbPort.getId())
!= portInfo.mUsbPortStatus.isConnected())) {
@@ -731,6 +1032,17 @@
StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED,
portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis);
}
+
+ if (!mContaminantStatus.containsKey(portInfo.mUsbPort.getId())
+ || (mContaminantStatus.get(portInfo.mUsbPort.getId())
+ != portInfo.mUsbPortStatus.getContaminantDetectionStatus())) {
+ mContaminantStatus.put(portInfo.mUsbPort.getId(),
+ portInfo.mUsbPortStatus.getContaminantDetectionStatus());
+ StatsLog.write(StatsLog.USB_CONTAMINANT_REPORTED,
+ portInfo.mUsbPort.getId(),
+ convertContaminantDetectionStatusToProto(
+ portInfo.mUsbPortStatus.getContaminantDetectionStatus()));
+ }
}
private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
@@ -759,6 +1071,11 @@
}
break;
}
+ case MSG_SYSTEM_READY: {
+ mNotificationManager = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ break;
+ }
}
}
};
@@ -784,8 +1101,14 @@
// 0 when port is connected. Else reports the last connected duration
public long mLastConnectDurationMillis;
- PortInfo(@NonNull UsbManager usbManager, @NonNull String portId, int supportedModes) {
- mUsbPort = new UsbPort(usbManager, portId, supportedModes);
+ PortInfo(@NonNull UsbManager usbManager, @NonNull String portId, int supportedModes,
+ int supportedContaminantProtectionModes,
+ boolean supportsEnableContaminantPresenceDetection,
+ boolean supportsEnableContaminantPresenceProtection) {
+ mUsbPort = new UsbPort(usbManager, portId, supportedModes,
+ supportedContaminantProtectionModes,
+ supportsEnableContaminantPresenceDetection,
+ supportsEnableContaminantPresenceProtection);
}
public boolean setStatus(int currentMode, boolean canChangeMode,
@@ -804,7 +1127,45 @@
|| mUsbPortStatus.getSupportedRoleCombinations()
!= supportedRoleCombinations) {
mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
- supportedRoleCombinations);
+ supportedRoleCombinations, UsbPortStatus.CONTAMINANT_PROTECTION_NONE,
+ UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED);
+ dispositionChanged = true;
+ }
+
+ if (mUsbPortStatus.isConnected() && mConnectedAtMillis == 0) {
+ mConnectedAtMillis = SystemClock.elapsedRealtime();
+ mLastConnectDurationMillis = 0;
+ } else if (!mUsbPortStatus.isConnected() && mConnectedAtMillis != 0) {
+ mLastConnectDurationMillis = SystemClock.elapsedRealtime() - mConnectedAtMillis;
+ mConnectedAtMillis = 0;
+ }
+
+ return dispositionChanged;
+ }
+
+ public boolean setStatus(int currentMode, boolean canChangeMode,
+ int currentPowerRole, boolean canChangePowerRole,
+ int currentDataRole, boolean canChangeDataRole,
+ int supportedRoleCombinations, int contaminantProtectionStatus,
+ int contaminantDetectionStatus) {
+ boolean dispositionChanged = false;
+
+ mCanChangeMode = canChangeMode;
+ mCanChangePowerRole = canChangePowerRole;
+ mCanChangeDataRole = canChangeDataRole;
+ if (mUsbPortStatus == null
+ || mUsbPortStatus.getCurrentMode() != currentMode
+ || mUsbPortStatus.getCurrentPowerRole() != currentPowerRole
+ || mUsbPortStatus.getCurrentDataRole() != currentDataRole
+ || mUsbPortStatus.getSupportedRoleCombinations()
+ != supportedRoleCombinations
+ || mUsbPortStatus.getContaminantProtectionStatus()
+ != contaminantProtectionStatus
+ || mUsbPortStatus.getContaminantDetectionStatus()
+ != contaminantDetectionStatus) {
+ mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
+ supportedRoleCombinations, contaminantProtectionStatus,
+ contaminantDetectionStatus);
dispositionChanged = true;
}
@@ -855,32 +1216,54 @@
private static final class RawPortInfo implements Parcelable {
public final String portId;
public final int supportedModes;
+ public final int supportedContaminantProtectionModes;
public int currentMode;
public boolean canChangeMode;
public int currentPowerRole;
public boolean canChangePowerRole;
public int currentDataRole;
public boolean canChangeDataRole;
+ public boolean supportsEnableContaminantPresenceProtection;
+ public int contaminantProtectionStatus;
+ public boolean supportsEnableContaminantPresenceDetection;
+ public int contaminantDetectionStatus;
RawPortInfo(String portId, int supportedModes) {
this.portId = portId;
this.supportedModes = supportedModes;
+ this.supportedContaminantProtectionModes = UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
+ this.supportsEnableContaminantPresenceProtection = false;
+ this.contaminantProtectionStatus = UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
+ this.supportsEnableContaminantPresenceDetection = false;
+ this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
}
- RawPortInfo(String portId, int supportedModes,
+ RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes,
int currentMode, boolean canChangeMode,
int currentPowerRole, boolean canChangePowerRole,
- int currentDataRole, boolean canChangeDataRole) {
+ int currentDataRole, boolean canChangeDataRole,
+ boolean supportsEnableContaminantPresenceProtection,
+ int contaminantProtectionStatus,
+ boolean supportsEnableContaminantPresenceDetection,
+ int contaminantDetectionStatus) {
this.portId = portId;
this.supportedModes = supportedModes;
+ this.supportedContaminantProtectionModes = supportedContaminantProtectionModes;
this.currentMode = currentMode;
this.canChangeMode = canChangeMode;
this.currentPowerRole = currentPowerRole;
this.canChangePowerRole = canChangePowerRole;
this.currentDataRole = currentDataRole;
this.canChangeDataRole = canChangeDataRole;
+ this.supportsEnableContaminantPresenceProtection =
+ supportsEnableContaminantPresenceProtection;
+ this.contaminantProtectionStatus = contaminantProtectionStatus;
+ this.supportsEnableContaminantPresenceDetection =
+ supportsEnableContaminantPresenceDetection;
+ this.contaminantDetectionStatus = contaminantDetectionStatus;
}
+
@Override
public int describeContents() {
return 0;
@@ -890,35 +1273,50 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(portId);
dest.writeInt(supportedModes);
+ dest.writeInt(supportedContaminantProtectionModes);
dest.writeInt(currentMode);
dest.writeByte((byte) (canChangeMode ? 1 : 0));
dest.writeInt(currentPowerRole);
dest.writeByte((byte) (canChangePowerRole ? 1 : 0));
dest.writeInt(currentDataRole);
dest.writeByte((byte) (canChangeDataRole ? 1 : 0));
+ dest.writeBoolean(supportsEnableContaminantPresenceProtection);
+ dest.writeInt(contaminantProtectionStatus);
+ dest.writeBoolean(supportsEnableContaminantPresenceDetection);
+ dest.writeInt(contaminantDetectionStatus);
}
public static final Parcelable.Creator<RawPortInfo> CREATOR =
new Parcelable.Creator<RawPortInfo>() {
- @Override
- public RawPortInfo createFromParcel(Parcel in) {
- String id = in.readString();
- int supportedModes = in.readInt();
- int currentMode = in.readInt();
- boolean canChangeMode = in.readByte() != 0;
- int currentPowerRole = in.readInt();
- boolean canChangePowerRole = in.readByte() != 0;
- int currentDataRole = in.readInt();
- boolean canChangeDataRole = in.readByte() != 0;
- return new RawPortInfo(id, supportedModes, currentMode, canChangeMode,
- currentPowerRole, canChangePowerRole,
- currentDataRole, canChangeDataRole);
- }
+ @Override
+ public RawPortInfo createFromParcel(Parcel in) {
+ String id = in.readString();
+ int supportedModes = in.readInt();
+ int supportedContaminantProtectionModes = in.readInt();
+ int currentMode = in.readInt();
+ boolean canChangeMode = in.readByte() != 0;
+ int currentPowerRole = in.readInt();
+ boolean canChangePowerRole = in.readByte() != 0;
+ int currentDataRole = in.readInt();
+ boolean canChangeDataRole = in.readByte() != 0;
+ boolean supportsEnableContaminantPresenceProtection = in.readBoolean();
+ int contaminantProtectionStatus = in.readInt();
+ boolean supportsEnableContaminantPresenceDetection = in.readBoolean();
+ int contaminantDetectionStatus = in.readInt();
+ return new RawPortInfo(id, supportedModes,
+ supportedContaminantProtectionModes, currentMode, canChangeMode,
+ currentPowerRole, canChangePowerRole,
+ currentDataRole, canChangeDataRole,
+ supportsEnableContaminantPresenceProtection,
+ contaminantProtectionStatus,
+ supportsEnableContaminantPresenceDetection,
+ contaminantDetectionStatus);
+ }
- @Override
- public RawPortInfo[] newArray(int size) {
- return new RawPortInfo[size];
- }
- };
+ @Override
+ public RawPortInfo[] newArray(int size) {
+ return new RawPortInfo[size];
+ }
+ };
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 9115477..4be68b8 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -554,6 +554,21 @@
}
@Override
+ public void enableContaminantDetection(String portId, boolean enable) {
+ Preconditions.checkNotNull(portId, "portId must not be null");
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mPortManager != null) {
+ mPortManager.enableContaminantDetection(portId, enable, null);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public void setUsbDeviceConnectionHandler(ComponentName usbDeviceConnectionHandler) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
synchronized (mLock) {
@@ -747,6 +762,15 @@
mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
"", 0);
}
+ } else if ("set-contaminant-status".equals(args[0]) && args.length == 3) {
+ final String portId = args[1];
+ final Boolean wet = Boolean.parseBoolean(args[2]);
+ if (mPortManager != null) {
+ mPortManager.simulateContaminantStatus(portId, wet, pw);
+ pw.println();
+ mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
+ "", 0);
+ }
} else if ("ports".equals(args[0]) && args.length == 1) {
if (mPortManager != null) {
mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
@@ -791,6 +815,11 @@
pw.println(" dumpsys usb connect-port \"matrix\" ufp sink device");
pw.println(" dumpsys usb reset");
pw.println();
+ pw.println("Example simulate contaminant status:");
+ pw.println(" dumpsys usb add-port \"matrix\" ufp");
+ pw.println(" dumpsys usb set-contaminant-status \"matrix\" true");
+ pw.println(" dumpsys usb set-contaminant-status \"matrix\" false");
+ pw.println();
pw.println("Example USB device descriptors:");
pw.println(" dumpsys usb dump-descriptors -dump-short");
pw.println(" dumpsys usb dump-descriptors -dump-tree");
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 613c4ff..bb01f04 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -19,9 +19,6 @@
import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-
-import com.android.internal.app.IVoiceActionCheckCallback;
-import com.android.server.wm.ActivityTaskManagerInternal;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -62,8 +59,9 @@
import android.util.Log;
import android.util.Slog;
-import com.android.internal.app.IVoiceInteractionSessionListener;
+import com.android.internal.app.IVoiceActionCheckCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.PackageMonitor;
@@ -75,6 +73,7 @@
import com.android.server.SystemService;
import com.android.server.UiThread;
import com.android.server.soundtrigger.SoundTriggerInternal;
+import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -1205,6 +1204,57 @@
mSoundTriggerInternal.dump(fd, pw, args);
}
+ @Override
+ public void setTranscription(String transcription) {
+ synchronized (this) {
+ final int size = mVoiceInteractionSessionListeners.beginBroadcast();
+ for (int i = 0; i < size; ++i) {
+ final IVoiceInteractionSessionListener listener =
+ mVoiceInteractionSessionListeners.getBroadcastItem(i);
+ try {
+ listener.onTranscriptionUpdate(transcription);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering voice transcription.", e);
+ }
+ }
+ mVoiceInteractionSessionListeners.finishBroadcast();
+ }
+ }
+
+ @Override
+ public void clearTranscription(boolean immediate) {
+ synchronized (this) {
+ final int size = mVoiceInteractionSessionListeners.beginBroadcast();
+ for (int i = 0; i < size; ++i) {
+ final IVoiceInteractionSessionListener listener =
+ mVoiceInteractionSessionListeners.getBroadcastItem(i);
+ try {
+ listener.onTranscriptionComplete(immediate);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering transcription complete event.", e);
+ }
+ }
+ mVoiceInteractionSessionListeners.finishBroadcast();
+ }
+ }
+
+ @Override
+ public void setVoiceState(int state) {
+ synchronized (this) {
+ final int size = mVoiceInteractionSessionListeners.beginBroadcast();
+ for (int i = 0; i < size; ++i) {
+ final IVoiceInteractionSessionListener listener =
+ mVoiceInteractionSessionListeners.getBroadcastItem(i);
+ try {
+ listener.onVoiceStateChange(state);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering voice state change.", e);
+ }
+ }
+ mVoiceInteractionSessionListeners.finishBroadcast();
+ }
+ }
+
private void enforceCallingPermission(String permission) {
if (mContext.checkCallingOrSelfPermission(permission)
!= PackageManager.PERMISSION_GRANTED) {
diff --git a/startop/OWNERS b/startop/OWNERS
index bfe96d3..5cf9582 100644
--- a/startop/OWNERS
+++ b/startop/OWNERS
@@ -2,4 +2,5 @@
chriswailes@google.com
eholk@google.com
iam@google.com
+mathieuc@google.com
sehr@google.com
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 826ad82..818ebd9 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.app.Service;
@@ -32,6 +33,9 @@
import com.android.internal.telecom.ICallScreeningAdapter;
import com.android.internal.telecom.ICallScreeningService;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* This service can be implemented by the default dialer (see
* {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
@@ -88,6 +92,128 @@
* </pre>
*/
public abstract class CallScreeningService extends Service {
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = { "CALL_DURATION_" },
+ value = {CALL_DURATION_VERY_SHORT, CALL_DURATION_SHORT, CALL_DURATION_MEDIUM,
+ CALL_DURATION_LONG})
+ public @interface CallDuration {}
+
+ /**
+ * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+ * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+ * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The
+ * {@link CallScreeningService} can use this as a signal for training nuisance detection
+ * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of
+ * identifying call log information to the {@link CallScreeningService}.
+ * <p>
+ * Indicates the call was < 3 seconds in duration.
+ */
+ public static final int CALL_DURATION_VERY_SHORT = 1;
+
+ /**
+ * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+ * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+ * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The
+ * {@link CallScreeningService} can use this as a signal for training nuisance detection
+ * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of
+ * identifying call log information to the {@link CallScreeningService}.
+ * <p>
+ * Indicates the call was greater than 3 seconds, but less than 60 seconds in duration.
+ */
+ public static final int CALL_DURATION_SHORT = 2;
+
+ /**
+ * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+ * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+ * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The
+ * {@link CallScreeningService} can use this as a signal for training nuisance detection
+ * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of
+ * identifying call log information to the {@link CallScreeningService}.
+ * <p>
+ * Indicates the call was greater than 60 seconds, but less than 120 seconds in duration.
+ */
+ public static final int CALL_DURATION_MEDIUM = 3;
+
+ /**
+ * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+ * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+ * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The
+ * {@link CallScreeningService} can use this as a signal for training nuisance detection
+ * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of
+ * identifying call log information to the {@link CallScreeningService}.
+ * <p>
+ * Indicates the call was greater than 120 seconds.
+ */
+ public static final int CALL_DURATION_LONG = 4;
+
+ /**
+ * Telecom sends this intent to the {@link CallScreeningService} which the user has chosen to
+ * fill the call screening role when the user indicates through the default dialer whether a
+ * call is a nuisance call or not (see
+ * {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).
+ * <p>
+ * The following extra values are provided for the call:
+ * <ol>
+ * <li>{@link #EXTRA_CALL_HANDLE} - the handle of the call.</li>
+ * <li>{@link #EXTRA_IS_NUISANCE} - {@code true} if the user reported the call as a nuisance
+ * call, {@code false} otherwise.</li>
+ * <li>{@link #EXTRA_CALL_TYPE} - reports the type of call (incoming, rejected, missed,
+ * blocked).</li>
+ * <li>{@link #EXTRA_CALL_DURATION} - the duration of the call (see
+ * {@link #EXTRA_CALL_DURATION} for valid values).</li>
+ * </ol>
+ * <p>
+ * {@link CallScreeningService} implementations which want to track whether the user reports
+ * calls are nuisance calls should use declare a broadcast receiver in their manifest for this
+ * intent.
+ * <p>
+ * Note: Only {@link CallScreeningService} implementations which have provided
+ * {@link CallIdentification} information for calls at some point will receive this intent.
+ */
+ public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED =
+ "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED";
+
+ /**
+ * Extra used to provide the handle of the call for
+ * {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}. The call handle is reported as a
+ * {@link Uri}.
+ */
+ public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE";
+
+ /**
+ * Boolean extra used to indicate whether the user reported a call as a nuisance call.
+ * When {@code true}, the user reported that a call was a nuisance call, {@code false}
+ * otherwise. Sent with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}.
+ */
+ public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE";
+
+ /**
+ * Integer extra used with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report the type of
+ * call. Valid values are:
+ * <UL>
+ * <li>{@link android.provider.CallLog.Calls#MISSED_TYPE}</li>
+ * <li>{@link android.provider.CallLog.Calls#INCOMING_TYPE}</li>
+ * <li>{@link android.provider.CallLog.Calls#BLOCKED_TYPE}</li>
+ * <li>{@link android.provider.CallLog.Calls#REJECTED_TYPE}</li>
+ * </UL>
+ */
+ public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE";
+
+ /**
+ * Integer extra used to with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report how long
+ * the call lasted. Valid values are:
+ * <UL>
+ * <LI>{@link #CALL_DURATION_VERY_SHORT}</LI>
+ * <LI>{@link #CALL_DURATION_SHORT}</LI>
+ * <LI>{@link #CALL_DURATION_MEDIUM}</LI>
+ * <LI>{@link #CALL_DURATION_LONG}</LI>
+ * </UL>
+ */
+ public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
+
/**
* The {@link Intent} that must be declared as handled by the service.
*/
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index a39e885..7d1f8ce 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.telecom.Connection.VideoProvider;
@@ -64,6 +65,10 @@
public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {}
public void onExtrasChanged(Conference c, Bundle extras) {}
public void onExtrasRemoved(Conference c, List<String> keys) {}
+ public void onConferenceStateChanged(Conference c, boolean isConference) {}
+ public void onAddressChanged(Conference c, Uri newAddress, int presentation) {}
+ public void onCallerDisplayNameChanged(
+ Conference c, String callerDisplayName, int presentation) {}
}
private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
@@ -946,6 +951,62 @@
public void onExtrasChanged(Bundle extras) {}
/**
+ * Set whether Telecom should treat this {@link Conference} as a conference call or if it
+ * should treat it as a single-party call.
+ * This method is used as part of a workaround regarding IMS conference calls and user
+ * expectation. In IMS, once a conference is formed, the UE is connected to an IMS conference
+ * server. If all participants of the conference drop out of the conference except for one, the
+ * UE is still connected to the IMS conference server. At this point, the user logically
+ * assumes they're no longer in a conference, yet the underlying network actually is.
+ * To help provide a better user experiece, IMS conference calls can pretend to actually be a
+ * single-party call when the participant count drops to 1. Although the dialer/phone app
+ * could perform this trickery, it makes sense to do this in Telephony since a fix there will
+ * ensure that bluetooth head units, auto and wearable apps all behave consistently.
+ *
+ * @param isConference {@code true} if this {@link Conference} should be treated like a
+ * conference call, {@code false} if it should be treated like a single-party call.
+ * @hide
+ */
+ public void setConferenceState(boolean isConference) {
+ for (Listener l : mListeners) {
+ l.onConferenceStateChanged(this, isConference);
+ }
+ }
+
+ /**
+ * Sets the address of this {@link Conference}. Used when {@link #setConferenceState(boolean)}
+ * is called to mark a conference temporarily as NOT a conference.
+ *
+ * @param address The new address.
+ * @param presentation The presentation requirements for the address.
+ * See {@link TelecomManager} for valid values.
+ * @hide
+ */
+ public final void setAddress(Uri address, int presentation) {
+ Log.d(this, "setAddress %s", address);
+ for (Listener l : mListeners) {
+ l.onAddressChanged(this, address, presentation);
+ }
+ }
+
+ /**
+ * Sets the caller display name (CNAP) of this {@link Conference}. Used when
+ * {@link #setConferenceState(boolean)} is called to mark a conference temporarily as NOT a
+ * conference.
+ *
+ * @param callerDisplayName The new display name.
+ * @param presentation The presentation requirements for the handle.
+ * See {@link TelecomManager} for valid values.
+ * @hide
+ */
+ public final void setCallerDisplayName(String callerDisplayName, int presentation) {
+ Log.d(this, "setCallerDisplayName %s", callerDisplayName);
+ for (Listener l : mListeners) {
+ l.onCallerDisplayNameChanged(this, callerDisplayName, presentation);
+ }
+ }
+
+ /**
* Handles a change to extras received from Telecom.
*
* @param extras The new extras.
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 4d5f5e1..9bafbe0 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1254,6 +1254,31 @@
mAdapter.removeExtras(id, keys);
}
}
+
+ @Override
+ public void onConferenceStateChanged(Conference c, boolean isConference) {
+ String id = mIdByConference.get(c);
+ if (id != null) {
+ mAdapter.setConferenceState(id, isConference);
+ }
+ }
+
+ @Override
+ public void onAddressChanged(Conference c, Uri newAddress, int presentation) {
+ String id = mIdByConference.get(c);
+ if (id != null) {
+ mAdapter.setAddress(id, newAddress, presentation);
+ }
+ }
+
+ @Override
+ public void onCallerDisplayNameChanged(Conference c, String callerDisplayName,
+ int presentation) {
+ String id = mIdByConference.get(c);
+ if (id != null) {
+ mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
+ }
+ }
};
private final Connection.Listener mConnectionListener = new Connection.Listener() {
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index 520e7ed..6c3f4f3 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -653,4 +653,22 @@
}
}
}
+
+ /**
+ * Sets whether a conference is treated as a conference or a single party call.
+ * See {@link Conference#setConferenceState(boolean)} for more information.
+ *
+ * @param callId The ID of the telecom call.
+ * @param isConference {@code true} if this call should be treated as a conference,
+ * {@code false} otherwise.
+ */
+ void setConferenceState(String callId, boolean isConference) {
+ Log.v(this, "setConferenceState: %s %b", callId, isConference);
+ for (IConnectionServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.setConferenceState(callId, isConference, Log.getExternalSession());
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
}
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index 78d65e6..f99b218 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -74,6 +74,7 @@
private static final int MSG_ON_RTT_UPGRADE_REQUEST = 33;
private static final int MSG_SET_PHONE_ACCOUNT_CHANGED = 34;
private static final int MSG_CONNECTION_SERVICE_FOCUS_RELEASED = 35;
+ private static final int MSG_SET_CONFERENCE_STATE = 36;
private final IConnectionServiceAdapter mDelegate;
@@ -333,6 +334,14 @@
case MSG_CONNECTION_SERVICE_FOCUS_RELEASED:
mDelegate.onConnectionServiceFocusReleased(null /*Session.Info*/);
break;
+ case MSG_SET_CONFERENCE_STATE:
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ mDelegate.setConferenceState((String) args.arg1, (Boolean) args.arg2,
+ (Session.Info) args.arg3);
+ } finally {
+ args.recycle();
+ }
}
}
};
@@ -615,6 +624,16 @@
public void resetConnectionTime(String callId, Session.Info sessionInfo) {
// Do nothing
}
+
+ @Override
+ public void setConferenceState(String callId, boolean isConference,
+ Session.Info sessionInfo) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = isConference;
+ args.arg3 = sessionInfo;
+ mHandler.obtainMessage(MSG_SET_CONFERENCE_STATE, args).sendToTarget();
+ }
};
public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) {
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 9821dcb..744544e 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -471,6 +471,12 @@
public void resetConnectionTime(String callId, Session.Info sessionInfo) {
// Do nothing
}
+
+ @Override
+ public void setConferenceState(String callId, boolean isConference,
+ Session.Info sessionInfo) {
+ // Do nothing
+ }
};
private final ConnectionServiceAdapterServant mServant =
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 6c4b1af8..12a5344 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -793,15 +793,17 @@
* <p>
* Apps must be prepared for this method to return {@code null}, indicating that there currently
* exists no user-chosen default {@code PhoneAccount}.
+ * <p>
+ * The default dialer has access to use this method.
*
* @return The user outgoing phone account selected by the user.
- * @hide
*/
- @UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
try {
if (isServiceConnected()) {
- return getTelecomService().getUserSelectedOutgoingPhoneAccount();
+ return getTelecomService().getUserSelectedOutgoingPhoneAccount(
+ mContext.getOpPackageName());
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#getUserSelectedOutgoingPhoneAccount", e);
@@ -810,10 +812,14 @@
}
/**
- * Sets the user-chosen default for making outgoing phone calls.
+ * Sets the user-chosen default {@link PhoneAccountHandle} for making outgoing phone calls.
+ *
+ * @param accountHandle The {@link PhoneAccountHandle} which will be used by default for making
+ * outgoing voice calls.
* @hide
*/
- @UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @SystemApi
public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
try {
if (isServiceConnected()) {
@@ -1964,6 +1970,33 @@
}
/**
+ * Called by the default dialer to report to Telecom when the user has marked a previous
+ * incoming call as a nuisance call or not.
+ * <p>
+ * Where the user has chosen a {@link CallScreeningService} to fill the call screening role,
+ * Telecom will notify that {@link CallScreeningService} of the user's report.
+ * <p>
+ * Requires that the caller is the default dialer app.
+ *
+ * @param handle The phone number of an incoming call which the user is reporting as either a
+ * nuisance of non-nuisance call.
+ * @param isNuisanceCall {@code true} if the user is reporting the call as a nuisance call,
+ * {@code false} if the user is reporting the call as a non-nuisance call.
+ */
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ public void reportNuisanceCallStatus(@NonNull Uri handle, boolean isNuisanceCall) {
+ ITelecomService service = getTelecomService();
+ if (service != null) {
+ try {
+ service.reportNuisanceCallStatus(handle, isNuisanceCall,
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelecomService#showCallScreen", e);
+ }
+ }
+ }
+
+ /**
* Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity.
* @param intent The {@link Intent#ACTION_CALL} intent to handle.
* @hide
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index 0157a58..76ac88e 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -123,4 +123,6 @@
void onConnectionServiceFocusReleased(in Session.Info sessionInfo);
void resetConnectionTime(String callIdi, in Session.Info sessionInfo);
+
+ void setConferenceState(String callId, boolean isConference, in Session.Info sessionInfo);
}
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 954a709..5030f90 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -45,7 +45,7 @@
/**
* @see TelecomServiceImpl#getUserSelectedOutgoingPhoneAccount
*/
- PhoneAccountHandle getUserSelectedOutgoingPhoneAccount();
+ PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(String callingPackage);
/**
* @see TelecomServiceImpl#setUserSelectedOutgoingPhoneAccount
@@ -285,6 +285,8 @@
*/
boolean isInEmergencyCall();
+ oneway void reportNuisanceCallStatus(in Uri address, boolean isNuisance, String callingPackage);
+
/**
* @see TelecomServiceImpl#handleCallIntent
*/
@@ -299,4 +301,5 @@
void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded);
void setTestAutoModeApp(String packageName);
+
}
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 15abdb7..5482270 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -2375,6 +2375,9 @@
/**
* Contains message parts.
+ *
+ * To avoid issues where applications might cache a part ID, the ID of a deleted part must
+ * not be reused to point at a new part.
*/
public static final class Part implements BaseColumns {
@@ -2386,6 +2389,12 @@
}
/**
+ * The {@code content://} style URL for this table. Can be appended with a part ID to
+ * address individual parts.
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(Mms.CONTENT_URI, "part");
+
+ /**
* The identifier of the message which this part belongs to.
* <P>Type: INTEGER</P>
*/
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 312b318..a33b44c 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2508,10 +2508,18 @@
public static final String KEY_GPS_LOCK_STRING = KEY_PREFIX + "gps_lock";
/**
- * SUPL NI emergency extension time in seconds. Default to "0".
+ * Control Plane / SUPL NI emergency extension time in seconds. Default to "0".
*/
public static final String KEY_ES_EXTENSION_SEC = KEY_PREFIX + "es_extension_sec";
+ /**
+ * Space separated list of Android package names of proxy applications representing
+ * the non-framework entities requesting location directly from GNSS without involving
+ * the framework, as managed by IGnssVisibilityControl.hal. For example,
+ * "com.example.mdt com.example.ims".
+ */
+ public static final String KEY_NFW_PROXY_APPS = KEY_PREFIX + "nfw_proxy_apps";
+
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
defaults.putBoolean(KEY_PERSIST_LPP_MODE_BOOL, true);
@@ -2525,6 +2533,7 @@
defaults.putString(KEY_A_GLONASS_POS_PROTOCOL_SELECT_STRING, "0");
defaults.putString(KEY_GPS_LOCK_STRING, "3");
defaults.putString(KEY_ES_EXTENSION_SEC, "0");
+ defaults.putString(KEY_NFW_PROXY_APPS, "");
return defaults;
}
}
diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java
index 7b29f69..30e641d 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -148,8 +148,9 @@
/**
* Return the Bit Error Rate
- * @returns the bit error rate (0-7, 99) as defined in TS 27.007 8.5 or UNAVAILABLE.
- * @hide
+ *
+ * @return the bit error rate (0-7, 99) as defined in TS 27.007 8.5 or
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE}.
*/
public int getBitErrorRate() {
return mBitErrorRate;
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index 26ec6de..ca264f7 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -42,7 +42,7 @@
// This series of errors as specified by the standards
// specified in ril.h
- /** Operator determined barring. */
+ /** Operator determined barring. (no retry) */
public static final int OPERATOR_BARRED = 0x08;
/** NAS signalling. */
public static final int NAS_SIGNALLING = 0x0E;
@@ -91,6 +91,11 @@
public static final int FILTER_SYTAX_ERROR = 0x2D;
/** Packet Data Protocol (PDP) without active traffic flow template (TFT). */
public static final int PDP_WITHOUT_ACTIVE_TFT = 0x2E;
+ /**
+ * UE requested to modify QoS parameters or the bearer control mode, which is not compatible
+ * with the selected bearer control mode.
+ */
+ public static final int ACTIVATION_REJECTED_BCM_VIOLATION = 0x30;
/** Packet Data Protocol (PDP) type IPv4 only allowed. */
public static final int ONLY_IPV4_ALLOWED = 0x32; /* no retry */
/** Packet Data Protocol (PDP) type IPv6 only allowed. */
@@ -103,6 +108,27 @@
public static final int PDN_CONN_DOES_NOT_EXIST = 0x36;
/** Multiple connections to a same PDN is not allowed. */
public static final int MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED = 0x37;
+ /**
+ * Network has already initiated the activation, modification, or deactivation of bearer
+ * resources that was requested by the UE.
+ */
+ public static final int COLLISION_WITH_NETWORK_INITIATED_REQUEST = 0x38;
+ /**
+ * Network supports IPv4v6 PDP type only. Non-IP type is not allowed. In LTE mode of operation,
+ * this is a PDN throttling cause code, meaning the UE may throttle further requests to the
+ * same APN.
+ */
+ public static final int ONLY_IPV4V6_ALLOWED = 0x39;
+ /**
+ * Network supports non-IP PDP type only. IPv4, IPv6 and IPv4v6 is not allowed. In LTE mode of
+ * operation, this is a PDN throttling cause code, meaning the UE can throttle further requests
+ * to the same APN.
+ */
+ public static final int ONLY_NON_IP_ALLOWED = 0x3A;
+ /** QCI (QoS Class Identifier) indicated in the UE request cannot be supported. */
+ public static final int UNSUPPORTED_QCI_VALUE = 0x3B;
+ /** Procedure requested by the UE was rejected because the bearer handling is not supported. */
+ public static final int BEARER_HANDLING_NOT_SUPPORTED = 0x3C;
/** Max number of Packet Data Protocol (PDP) context reached. */
public static final int ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED = 0x41;
/** Unsupported APN in current public land mobile network (PLMN). */
@@ -146,6 +172,742 @@
public static final int EMM_ACCESS_BARRED_INFINITE_RETRY = 0x79;
/** Authentication failure on emergency call. */
public static final int AUTH_FAILURE_ON_EMERGENCY_CALL = 0x7A;
+ /** Not receiving a DNS address that was mandatory. */
+ public static final int INVALID_DNS_ADDR = 0x7B;
+ /** Not receiving either a PCSCF or a DNS address, one of them being mandatory. */
+ public static final int INVALID_PCSCF_OR_DNS_ADDRESS = 0x7C;
+ /** Emergency call bring up on a different ePDG. */
+ public static final int CALL_PREEMPT_BY_EMERGENCY_APN = 0x7F;
+ /** UE performs a detach or disconnect PDN action based on TE requirements. */
+ public static final int UE_INITIATED_DETACH_OR_DISCONNECT = 0x80;
+
+ /** Reason unspecified for foreign agent rejected MIP (Mobile IP) registration. */
+ public static final int MIP_FA_REASON_UNSPECIFIED = 0x7D0;
+ /** Foreign agent administratively prohibited MIP (Mobile IP) registration. */
+ public static final int MIP_FA_ADMIN_PROHIBITED = 0x7D1;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of insufficient resources. */
+ public static final int MIP_FA_INSUFFICIENT_RESOURCES = 0x7D2;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of MN-AAA authenticator was
+ * wrong.
+ */
+ public static final int MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE = 0x7D3;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of home agent authentication
+ * failure.
+ */
+ public static final int MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE = 0x7D4;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of requested lifetime was too
+ * long.
+ */
+ public static final int MIP_FA_REQUESTED_LIFETIME_TOO_LONG = 0x7D5;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of malformed request. */
+ public static final int MIP_FA_MALFORMED_REQUEST = 0x7D6;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of malformed reply. */
+ public static final int MIP_FA_MALFORMED_REPLY = 0x7D7;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of requested encapsulation was
+ * unavailable.
+ */
+ public static final int MIP_FA_ENCAPSULATION_UNAVAILABLE = 0x7D8;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration of VJ Header Compression was
+ * unavailable.
+ */
+ public static final int MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE = 0x7D9;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of reverse tunnel was
+ * unavailable.
+ */
+ public static final int MIP_FA_REVERSE_TUNNEL_UNAVAILABLE = 0x7DA;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of reverse tunnel was mandatory
+ * but not requested by device.
+ */
+ public static final int MIP_FA_REVERSE_TUNNEL_IS_MANDATORY = 0x7DB;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of delivery style was not
+ * supported.
+ */
+ public static final int MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED = 0x7DC;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of missing NAI (Network Access
+ * Identifier).
+ */
+ public static final int MIP_FA_MISSING_NAI = 0x7DD;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of missing Home Agent. */
+ public static final int MIP_FA_MISSING_HOME_AGENT = 0x7DE;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of missing Home Address. */
+ public static final int MIP_FA_MISSING_HOME_ADDRESS = 0x7DF;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of unknown challenge. */
+ public static final int MIP_FA_UNKNOWN_CHALLENGE = 0x7E0;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of missing challenge. */
+ public static final int MIP_FA_MISSING_CHALLENGE = 0x7E1;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of stale challenge. */
+ public static final int MIP_FA_STALE_CHALLENGE = 0x7E2;
+ /** Reason unspecified for home agent rejected MIP (Mobile IP) registration. */
+ public static final int MIP_HA_REASON_UNSPECIFIED = 0x7E3;
+ /** Home agent administratively prohibited MIP (Mobile IP) registration. */
+ public static final int MIP_HA_ADMIN_PROHIBITED = 0x7E4;
+ /** Home agent rejected MIP (Mobile IP) registration because of insufficient resources. */
+ public static final int MIP_HA_INSUFFICIENT_RESOURCES = 0x7E5;
+ /**
+ * Home agent rejected MIP (Mobile IP) registration because of MN-HA authenticator was
+ * wrong.
+ */
+ public static final int MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE = 0x7E6;
+ /**
+ * Home agent rejected MIP (Mobile IP) registration because of foreign agent authentication
+ * failure.
+ */
+ public static final int MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE = 0x7E7;
+ /** Home agent rejected MIP (Mobile IP) registration because of registration id mismatch. */
+ public static final int MIP_HA_REGISTRATION_ID_MISMATCH = 0x7E8;
+ /** Home agent rejected MIP (Mobile IP) registration because of malformed request. */
+ public static final int MIP_HA_MALFORMED_REQUEST = 0x7E9;
+ /** Home agent rejected MIP (Mobile IP) registration because of unknown home agent address. */
+ public static final int MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS = 0x7EA;
+ /**
+ * Home agent rejected MIP (Mobile IP) registration because of reverse tunnel was
+ * unavailable.
+ */
+ public static final int MIP_HA_REVERSE_TUNNEL_UNAVAILABLE = 0x7EB;
+ /**
+ * Home agent rejected MIP (Mobile IP) registration because of reverse tunnel is mandatory but
+ * not requested by device.
+ */
+ public static final int MIP_HA_REVERSE_TUNNEL_IS_MANDATORY = 0x7EC;
+ /** Home agent rejected MIP (Mobile IP) registration because of encapsulation unavailable. */
+ public static final int MIP_HA_ENCAPSULATION_UNAVAILABLE = 0x7ED;
+ /** Tearing down is in progress. */
+ public static final int CLOSE_IN_PROGRESS = 0x7EE;
+ /** Brought down by the network. */
+ public static final int NETWORK_INITIATED_TERMINATION = 0x7EF;
+ /** Another application in modem preempts the data call. */
+ public static final int MODEM_APP_PREEMPTED = 0x7F0;
+ /**
+ * IPV4 PDN is in throttled state due to network providing only IPV6 address during the
+ * previous VSNCP bringup (subs_limited_to_v6).
+ */
+ public static final int PDN_IPV4_CALL_DISALLOWED = 0x7F1;
+ /** IPV4 PDN is in throttled state due to previous VSNCP bringup failure(s). */
+ public static final int PDN_IPV4_CALL_THROTTLED = 0x7F2;
+ /**
+ * IPV6 PDN is in throttled state due to network providing only IPV4 address during the
+ * previous VSNCP bringup (subs_limited_to_v4).
+ */
+ public static final int PDN_IPV6_CALL_DISALLOWED = 0x7F3;
+ /** IPV6 PDN is in throttled state due to previous VSNCP bringup failure(s). */
+ public static final int PDN_IPV6_CALL_THROTTLED = 0x7F4;
+ /** Modem restart. */
+ public static final int MODEM_RESTART = 0x7F5;
+ /** PDP PPP calls are not supported. */
+ public static final int PDP_PPP_NOT_SUPPORTED = 0x7F6;
+ /** RAT on which the data call is attempted/connected is no longer the preferred RAT. */
+ public static final int UNPREFERRED_RAT = 0x7F7;
+ /** Physical link is in the process of cleanup. */
+ public static final int PHYSICAL_LINK_CLOSE_IN_PROGRESS = 0x7F8;
+ /** Interface bring up is attempted for an APN that is yet to be handed over to target RAT. */
+ public static final int APN_PENDING_HANDOVER = 0x7F9;
+ /** APN bearer type in the profile does not match preferred network mode. */
+ public static final int PROFILE_BEARER_INCOMPATIBLE = 0x7FA;
+ /** Card was refreshed or removed. */
+ public static final int SIM_CARD_CHANGED = 0x7FB;
+ /** Device is going into lower power mode or powering down. */
+ public static final int LOW_POWER_MODE_OR_POWERING_DOWN = 0x7FC;
+ /** APN has been disabled. */
+ public static final int APN_DISABLED = 0x7FD;
+ /** Maximum PPP inactivity timer expired. */
+ public static final int MAX_PPP_INACTIVITY_TIMER_EXPIRED = 0x7FE;
+ /** IPv6 address transfer failed. */
+ public static final int IPV6_ADDRESS_TRANSFER_FAILED = 0x7FF;
+ /** Target RAT swap failed. */
+ public static final int TRAT_SWAP_FAILED = 0x800;
+ /** Device falls back from eHRPD to HRPD. */
+ public static final int EHRPD_TO_HRPD_FALLBACK = 0x801;
+ /**
+ * UE is in MIP-only configuration but the MIP configuration fails on call bring up due to
+ * incorrect provisioning.
+ */
+ public static final int MIP_CONFIG_FAILURE = 0x802;
+ /**
+ * PDN inactivity timer expired due to no data transmission in a configurable duration of time.
+ */
+ public static final int PDN_INACTIVITY_TIMER_EXPIRED = 0x803;
+ /**
+ * IPv4 data call bring up is rejected because the UE already maintains the allotted maximum
+ * number of IPv4 data connections.
+ */
+ public static final int MAX_IPV4_CONNECTIONS = 0x804;
+ /**
+ * IPv6 data call bring up is rejected because the UE already maintains the allotted maximum
+ * number of IPv6 data connections.
+ */
+ public static final int MAX_IPV6_CONNECTIONS = 0x805;
+ /**
+ * New PDN bring up is rejected during interface selection because the UE has already allotted
+ * the available interfaces for other PDNs.
+ */
+ public static final int APN_MISMATCH = 0x806;
+ /**
+ * New call bring up is rejected since the existing data call IP type doesn't match the
+ * requested IP.
+ */
+ public static final int IP_VERSION_MISMATCH = 0x807;
+ /** Dial up networking (DUN) call bring up is rejected since UE is in eHRPD RAT. */
+ public static final int DUN_CALL_DISALLOWED = 0x808;
+ /*** Rejected/Brought down since UE is transition between EPC and NONEPC RAT. */
+ public static final int INTERNAL_EPC_NONEPC_TRANSITION = 0x809;
+ /** The current interface is being in use. */
+ public static final int INTERFACE_IN_USE = 0x80A;
+ /** PDN connection to the APN is disallowed on the roaming network. */
+ public static final int APN_DISALLOWED_ON_ROAMING = 0x80B;
+ /** APN-related parameters are changed. */
+ public static final int APN_PARAMETERS_CHANGED = 0x80C;
+ /** PDN is attempted to be brought up with NULL APN but NULL APN is not supported. */
+ public static final int NULL_APN_DISALLOWED = 0x80D;
+ /**
+ * Thermal level increases and causes calls to be torn down when normal mode of operation is
+ * not allowed.
+ */
+ public static final int THERMAL_MITIGATION = 0x80E;
+ /**
+ * PDN Connection to a given APN is disallowed because data is disabled from the device user
+ * interface settings.
+ */
+ public static final int DATA_SETTINGS_DISABLED = 0x80F;
+ /**
+ * PDN Connection to a given APN is disallowed because data roaming is disabled from the device
+ * user interface settings and the UE is roaming.
+ */
+ public static final int DATA_ROAMING_SETTINGS_DISABLED = 0x810;
+ /** DDS (Default data subscription) switch occurs. */
+ public static final int DDS_SWITCHED = 0x811;
+ /** PDN being brought up with an APN that is part of forbidden APN Name list. */
+ public static final int FORBIDDEN_APN_NAME = 0x812;
+ /** Default data subscription switch is in progress. */
+ public static final int DDS_SWITCH_IN_PROGRESS = 0x813;
+ /** Roaming is disallowed during call bring up. */
+ public static final int CALL_DISALLOWED_IN_ROAMING = 0x814;
+ /**
+ * UE is unable to bring up a non-IP data call because the device is not camped on a NB1 cell.
+ */
+ public static final int NON_IP_NOT_SUPPORTED = 0x815;
+ /** Non-IP PDN is in throttled state due to previous VSNCP bringup failure(s). */
+ public static final int PDN_NON_IP_CALL_THROTTLED = 0x816;
+ /** Non-IP PDN is in disallowed state due to the network providing only an IP address. */
+ public static final int PDN_NON_IP_CALL_DISALLOWED = 0x817;
+ /** Device in CDMA locked state. */
+ public static final int CDMA_LOCK = 0x818;
+ /** Received an intercept order from the base station. */
+ public static final int CDMA_INTERCEPT = 0x819;
+ /** Receiving a reorder from the base station. */
+ public static final int CDMA_REORDER = 0x81A;
+ /** Receiving a release from the base station with a SO (Service Option) Reject reason. */
+ public static final int CDMA_RELEASE_DUE_TO_SO_REJECTION = 0x81B;
+ /** Receiving an incoming call from the base station. */
+ public static final int CDMA_INCOMING_CALL = 0x81C;
+ /** Received an alert stop from the base station due to incoming only. */
+ public static final int CDMA_ALERT_STOP = 0x81D;
+ /**
+ * Channel acquisition failures. This indicates that device has failed acquiring all the
+ * channels in the PRL.
+ */
+ public static final int CHANNEL_ACQUISITION_FAILURE = 0x81E;
+ /** Maximum access probes transmitted. */
+ public static final int MAX_ACCESS_PROBE = 0x81F;
+ /** Concurrent service is not supported by base station. */
+ public static final int CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION = 0x820;
+ /** There was no response received from the base station. */
+ public static final int NO_RESPONSE_FROM_BASE_STATION = 0x821;
+ /** The base station rejecting the call. */
+ public static final int REJECTED_BY_BASE_STATION = 0x822;
+ /** The concurrent services requested were not compatible. */
+ public static final int CONCURRENT_SERVICES_INCOMPATIBLE = 0x823;
+ /** Device does not have CDMA service. */
+ public static final int NO_CDMA_SERVICE = 0x824;
+ /** RUIM not being present. */
+ public static final int RUIM_NOT_PRESENT = 0x825;
+ /** Receiving a retry order from the base station. */
+ public static final int CDMA_RETRY_ORDER = 0x826;
+ /** Access blocked by the base station. */
+ public static final int ACCESS_BLOCK = 0x827;
+ /** Access blocked by the base station for all mobile devices. */
+ public static final int ACCESS_BLOCK_ALL = 0x828;
+ /** Maximum access probes for the IS-707B call. */
+ public static final int IS707B_MAX_ACCESS_PROBES = 0x829;
+ /** Put device in thermal emergency. */
+ public static final int THERMAL_EMERGENCY = 0x82A;
+ /** In favor of a voice call or SMS when concurrent voice and data are not supported. */
+ public static final int CONCURRENT_SERVICES_NOT_ALLOWED = 0x82B;
+ /** The other clients rejected incoming call. */
+ public static final int INCOMING_CALL_REJECTED = 0x82C;
+ /** No service on the gateway. */
+ public static final int NO_SERVICE_ON_GATEWAY = 0x82D;
+ /** GPRS context is not available. */
+ public static final int NO_GPRS_CONTEXT = 0x82E;
+ /**
+ * Network refuses service to the MS because either an identity of the MS is not acceptable to
+ * the network or the MS does not pass the authentication check.
+ */
+ public static final int ILLEGAL_MS = 0x82F;
+ /** ME could not be authenticated and the ME used is not acceptable to the network. */
+ public static final int ILLEGAL_ME = 0x830;
+ /** Not allowed to operate either GPRS or non-GPRS services. */
+ public static final int GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED = 0x831;
+ /** MS is not allowed to operate GPRS services. */
+ public static final int GPRS_SERVICES_NOT_ALLOWED = 0x832;
+ /** No matching identity or context could be found in the network. */
+ public static final int MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK = 0x833;
+ /**
+ * Mobile reachable timer has expired, or the GMM context data related to the subscription does
+ * not exist in the SGSN.
+ */
+ public static final int IMPLICITLY_DETACHED = 0x834;
+ /**
+ * UE requests GPRS service, or the network initiates a detach request in a PLMN which does not
+ * offer roaming for GPRS services to that MS.
+ */
+ public static final int PLMN_NOT_ALLOWED = 0x835;
+ /**
+ * MS requests service, or the network initiates a detach request, in a location area where the
+ * HPLMN determines that the MS, by subscription, is not allowed to operate.
+ */
+ public static final int LOCATION_AREA_NOT_ALLOWED = 0x836;
+ /**
+ * UE requests GPRS service or the network initiates a detach request in a PLMN that does not
+ * offer roaming for GPRS services.
+ */
+ public static final int GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN = 0x837;
+ /** PDP context already exists. */
+ public static final int PDP_DUPLICATE = 0x838;
+ /** RAT change on the UE. */
+ public static final int UE_RAT_CHANGE = 0x839;
+ /** Network cannot serve a request from the MS due to congestion. */
+ public static final int CONGESTION = 0x83A;
+ /**
+ * MS requests an establishment of the radio access bearers for all active PDP contexts by
+ * sending a service request message indicating data to the network, but the SGSN does not have
+ * any active PDP context.
+ */
+ public static final int NO_PDP_CONTEXT_ACTIVATED = 0x83B;
+ /** Access class blocking restrictions for the current camped cell. */
+ public static final int ACCESS_CLASS_DSAC_REJECTION = 0x83C;
+ /** SM attempts PDP activation for a maximum of four attempts. */
+ public static final int PDP_ACTIVATE_MAX_RETRY_FAILED = 0x83D;
+ /** Radio access bearer failure. */
+ public static final int RADIO_ACCESS_BEARER_FAILURE = 0x83E;
+ /** Invalid EPS bearer identity in the request. */
+ public static final int ESM_UNKNOWN_EPS_BEARER_CONTEXT = 0x83F;
+ /** Data radio bearer is released by the RRC. */
+ public static final int DRB_RELEASED_BY_RRC = 0x840;
+ /** Indicate the connection was released. */
+ public static final int CONNECTION_RELEASED = 0x841;
+ /** UE is detached. */
+ public static final int EMM_DETACHED = 0x842;
+ /** Attach procedure is rejected by the network. */
+ public static final int EMM_ATTACH_FAILED = 0x843;
+ /** Attach procedure is started for EMC purposes. */
+ public static final int EMM_ATTACH_STARTED = 0x844;
+ /** Service request procedure failure. */
+ public static final int LTE_NAS_SERVICE_REQUEST_FAILED = 0x845;
+ /** Active dedicated bearer was requested using the same default bearer ID. */
+ public static final int DUPLICATE_BEARER_ID = 0x846;
+ /** Collision scenarios for the UE and network-initiated procedures. */
+ public static final int ESM_COLLISION_SCENARIOS = 0x847;
+ /** Bearer must be deactivated to synchronize with the network. */
+ public static final int ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK = 0x848;
+ /** Active dedicated bearer was requested for an existing default bearer. */
+ public static final int ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER = 0x849;
+ /** Bad OTA message is received from the network. */
+ public static final int ESM_BAD_OTA_MESSAGE = 0x84A;
+ /** Download server rejected the call. */
+ public static final int ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL = 0x84B;
+ /** PDN was disconnected by the downlaod server due to IRAT. */
+ public static final int ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT = 0x84C;
+ /** Dedicated bearer will be deactivated regardless of the network response. */
+ public static final int DS_EXPLICIT_DEACTIVATION = 0x84D;
+ /** No specific local cause is mentioned, usually a valid OTA cause. */
+ public static final int ESM_LOCAL_CAUSE_NONE = 0x84E;
+ /** Throttling is not needed for this service request failure. */
+ public static final int LTE_THROTTLING_NOT_REQUIRED = 0x84F;
+ /** Access control list check failure at the lower layer. */
+ public static final int ACCESS_CONTROL_LIST_CHECK_FAILURE = 0x850;
+ /** Service is not allowed on the requested PLMN. */
+ public static final int SERVICE_NOT_ALLOWED_ON_PLMN = 0x851;
+ /** T3417 timer expiration of the service request procedure. */
+ public static final int EMM_T3417_EXPIRED = 0x852;
+ /** Extended service request fails due to expiration of the T3417 EXT timer. */
+ public static final int EMM_T3417_EXT_EXPIRED = 0x853;
+ /** Transmission failure of radio resource control (RRC) uplink data. */
+ public static final int RRC_UPLINK_DATA_TRANSMISSION_FAILURE = 0x854;
+ /** Radio resource control (RRC) uplink data delivery failed due to a handover. */
+ public static final int RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER = 0x855;
+ /** Radio resource control (RRC) uplink data delivery failed due to a connection release. */
+ public static final int RRC_UPLINK_CONNECTION_RELEASE = 0x856;
+ /** Radio resource control (RRC) uplink data delivery failed due to a radio link failure. */
+ public static final int RRC_UPLINK_RADIO_LINK_FAILURE = 0x857;
+ /**
+ * Radio resource control (RRC) is not connected but the non-access stratum (NAS) sends an
+ * uplink data request.
+ */
+ public static final int RRC_UPLINK_ERROR_REQUEST_FROM_NAS = 0x858;
+ /** Radio resource control (RRC) connection failure at access stratum. */
+ public static final int RRC_CONNECTION_ACCESS_STRATUM_FAILURE = 0x859;
+ /**
+ * Radio resource control (RRC) connection establishment is aborted due to another procedure.
+ */
+ public static final int RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS = 0x85A;
+ /** Radio resource control (RRC) connection establishment failed due to access barrred. */
+ public static final int RRC_CONNECTION_ACCESS_BARRED = 0x85B;
+ /**
+ * Radio resource control (RRC) connection establishment failed due to cell reselection at
+ * access stratum.
+ */
+ public static final int RRC_CONNECTION_CELL_RESELECTION = 0x85C;
+ /**
+ * Connection establishment failed due to configuration failure at the radio resource control
+ * (RRC).
+ */
+ public static final int RRC_CONNECTION_CONFIG_FAILURE = 0x85D;
+ /** Radio resource control (RRC) connection could not be established in the time limit. */
+ public static final int RRC_CONNECTION_TIMER_EXPIRED = 0x85E;
+ /**
+ * Connection establishment failed due to a link failure at the radio resource control (RRC).
+ */
+ public static final int RRC_CONNECTION_LINK_FAILURE = 0x85F;
+ /**
+ * Connection establishment failed as the radio resource control (RRC) is not camped on any
+ * cell.
+ */
+ public static final int RRC_CONNECTION_CELL_NOT_CAMPED = 0x860;
+ /**
+ * Connection establishment failed due to a service interval failure at the radio resource
+ * control (RRC).
+ */
+ public static final int RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE = 0x861;
+ /**
+ * Radio resource control (RRC) connection establishment failed due to the network rejecting
+ * the UE connection request.
+ */
+ public static final int RRC_CONNECTION_REJECT_BY_NETWORK = 0x862;
+ /** Normal radio resource control (RRC) connection release. */
+ public static final int RRC_CONNECTION_NORMAL_RELEASE = 0x863;
+ /**
+ * Radio resource control (RRC) connection release failed due to radio link failure conditions.
+ */
+ public static final int RRC_CONNECTION_RADIO_LINK_FAILURE = 0x864;
+ /** Radio resource control (RRC) connection re-establishment failure. */
+ public static final int RRC_CONNECTION_REESTABLISHMENT_FAILURE = 0x865;
+ /** UE is out of service during the call register. */
+ public static final int RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER = 0x866;
+ /**
+ * Connection has been released by the radio resource control (RRC) due to an abort request.
+ */
+ public static final int RRC_CONNECTION_ABORT_REQUEST = 0x867;
+ /**
+ * Radio resource control (RRC) connection released due to a system information block read
+ * error.
+ */
+ public static final int RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR = 0x868;
+ /** Network-initiated detach with reattach. */
+ public static final int NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH = 0x869;
+ /** Network-initiated detach without reattach. */
+ public static final int NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH = 0x86A;
+ /** ESM procedure maximum attempt timeout failure. */
+ public static final int ESM_PROCEDURE_TIME_OUT = 0x86B;
+ /**
+ * No PDP exists with the given connection ID while modifying or deactivating or activation for
+ * an already active PDP.
+ */
+ public static final int INVALID_CONNECTION_ID = 0x86C;
+ /** Maximum NSAPIs have been exceeded during PDP activation. */
+ public static final int MAXIMIUM_NSAPIS_EXCEEDED = 0x86D;
+ /** Primary context for NSAPI does not exist. */
+ public static final int INVALID_PRIMARY_NSAPI = 0x86E;
+ /** Unable to encode the OTA message for MT PDP or deactivate PDP. */
+ public static final int CANNOT_ENCODE_OTA_MESSAGE = 0x86F;
+ /**
+ * Radio access bearer is not established by the lower layers during activation, modification,
+ * or deactivation.
+ */
+ public static final int RADIO_ACCESS_BEARER_SETUP_FAILURE = 0x870;
+ /** Expiration of the PDP establish timer with a maximum of five retries. */
+ public static final int PDP_ESTABLISH_TIMEOUT_EXPIRED = 0x871;
+ /** Expiration of the PDP modify timer with a maximum of four retries. */
+ public static final int PDP_MODIFY_TIMEOUT_EXPIRED = 0x872;
+ /** Expiration of the PDP deactivate timer with a maximum of four retries. */
+ public static final int PDP_INACTIVE_TIMEOUT_EXPIRED = 0x873;
+ /** PDP activation failed due to RRC_ABORT or a forbidden PLMN. */
+ public static final int PDP_LOWERLAYER_ERROR = 0x874;
+ /** MO PDP modify collision when the MT PDP is already in progress. */
+ public static final int PDP_MODIFY_COLLISION = 0x875;
+ /** Maximum size of the L2 message was exceeded. */
+ public static final int MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED = 0x876;
+ /** Non-access stratum (NAS) request was rejected by the network. */
+ public static final int NAS_REQUEST_REJECTED_BY_NETWORK = 0x877;
+ /**
+ * Radio resource control (RRC) connection establishment failure due to an error in the request
+ * message.
+ */
+ public static final int RRC_CONNECTION_INVALID_REQUEST = 0x878;
+ /**
+ * Radio resource control (RRC) connection establishment failure due to a change in the
+ * tracking area ID.
+ */
+ public static final int RRC_CONNECTION_TRACKING_AREA_ID_CHANGED = 0x879;
+ /**
+ * Radio resource control (RRC) connection establishment failure due to the RF was unavailable.
+ */
+ public static final int RRC_CONNECTION_RF_UNAVAILABLE = 0x87A;
+ /**
+ * Radio resource control (RRC) connection was aborted before deactivating the LTE stack due to
+ * a successful LTE to WCDMA/GSM/TD-SCDMA IRAT change.
+ */
+ public static final int RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE = 0x87B;
+ /**
+ * If the UE has an LTE radio link failure before security is established, the radio resource
+ * control (RRC) connection must be released and the UE must return to idle.
+ */
+ public static final int RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE = 0x87C;
+ /**
+ * Radio resource control (RRC) connection was aborted by the non-access stratum (NAS) after an
+ * IRAT to LTE IRAT handover.
+ */
+ public static final int RRC_CONNECTION_ABORTED_AFTER_HANDOVER = 0x87D;
+ /**
+ * Radio resource control (RRC) connection was aborted before deactivating the LTE stack after
+ * a successful LTE to GSM/EDGE IRAT cell change order procedure.
+ */
+ public static final int RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE = 0x87E;
+ /**
+ * Radio resource control (RRC) connection was aborted in the middle of a LTE to GSM IRAT cell
+ * change order procedure.
+ */
+ public static final int RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE = 0x87F;
+ /** IMSI present in the UE is unknown in the home subscriber server. */
+ public static final int IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER = 0x880;
+ /** IMEI of the UE is not accepted by the network. */
+ public static final int IMEI_NOT_ACCEPTED = 0x881;
+ /** EPS and non-EPS services are not allowed by the network. */
+ public static final int EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED = 0x882;
+ /** EPS services are not allowed in the PLMN. */
+ public static final int EPS_SERVICES_NOT_ALLOWED_IN_PLMN = 0x883;
+ /** Mobile switching center is temporarily unreachable. */
+ public static final int MSC_TEMPORARILY_NOT_REACHABLE = 0x884;
+ /** CS domain is not available. */
+ public static final int CS_DOMAIN_NOT_AVAILABLE = 0x885;
+ /** ESM level failure. */
+ public static final int ESM_FAILURE = 0x886;
+ /** MAC level failure. */
+ public static final int MAC_FAILURE = 0x887;
+ /** Synchronization failure. */
+ public static final int SYNCHRONIZATION_FAILURE = 0x888;
+ /** UE security capabilities mismatch. */
+ public static final int UE_SECURITY_CAPABILITIES_MISMATCH = 0x889;
+ /** Unspecified security mode reject. */
+ public static final int SECURITY_MODE_REJECTED = 0x88A;
+ /** Unacceptable non-EPS authentication. */
+ public static final int UNACCEPTABLE_NON_EPS_AUTHENTICATION = 0x88B;
+ /** CS fallback call establishment is not allowed. */
+ public static final int CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED = 0x88C;
+ /** No EPS bearer context was activated. */
+ public static final int NO_EPS_BEARER_CONTEXT_ACTIVATED = 0x88D;
+ /** Invalid EMM state. */
+ public static final int INVALID_EMM_STATE = 0x88E;
+ /** Non-Access Spectrum layer failure. */
+ public static final int NAS_LAYER_FAILURE = 0x88F;
+ /** Multiple PDP call feature is disabled. */
+ public static final int MULTIPLE_PDP_CALL_NOT_ALLOWED = 0x890;
+ /** Data call has been brought down because EMBMS is not enabled at the RRC layer. */
+ public static final int EMBMS_NOT_ENABLED = 0x891;
+ /** Data call was unsuccessfully transferred during the IRAT handover. */
+ public static final int IRAT_HANDOVER_FAILED = 0x892;
+ /** EMBMS data call has been successfully brought down. */
+ public static final int EMBMS_REGULAR_DEACTIVATION = 0x893;
+ /** Test loop-back data call has been successfully brought down. */
+ public static final int TEST_LOOPBACK_REGULAR_DEACTIVATION = 0x894;
+ /** Lower layer registration failure. */
+ public static final int LOWER_LAYER_REGISTRATION_FAILURE = 0x895;
+ /**
+ * Network initiates a detach on LTE with error cause ""data plan has been replenished or has
+ * expired.
+ */
+ public static final int DATA_PLAN_EXPIRED = 0x896;
+ /** UMTS interface is brought down due to handover from UMTS to iWLAN. */
+ public static final int UMTS_HANDOVER_TO_IWLAN = 0x897;
+ /** Received a connection deny due to general or network busy on EVDO network. */
+ public static final int EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY = 0x898;
+ /** Received a connection deny due to billing or authentication failure on EVDO network. */
+ public static final int EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE = 0x899;
+ /** HDR system has been changed due to redirection or the PRL was not preferred. */
+ public static final int EVDO_HDR_CHANGED = 0x89A;
+ /** Device exited HDR due to redirection or the PRL was not preferred. */
+ public static final int EVDO_HDR_EXITED = 0x89B;
+ /** Device does not have an HDR session. */
+ public static final int EVDO_HDR_NO_SESSION = 0x89C;
+ /** It is ending an HDR call origination in favor of a GPS fix. */
+ public static final int EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL = 0x89D;
+ /** Connection setup on the HDR system was time out. */
+ public static final int EVDO_HDR_CONNECTION_SETUP_TIMEOUT = 0x89E;
+ /** Device failed to acquire a co-located HDR for origination. */
+ public static final int FAILED_TO_ACQUIRE_COLOCATED_HDR = 0x89F;
+ /** OTASP commit is in progress. */
+ public static final int OTASP_COMMIT_IN_PROGRESS = 0x8A0;
+ /** Device has no hybrid HDR service. */
+ public static final int NO_HYBRID_HDR_SERVICE = 0x8A1;
+ /** HDR module could not be obtained because of the RF locked. */
+ public static final int HDR_NO_LOCK_GRANTED = 0x8A2;
+ /** DBM or SMS is in progress. */
+ public static final int DBM_OR_SMS_IN_PROGRESS = 0x8A3;
+ /** HDR module released the call due to fade. */
+ public static final int HDR_FADE = 0x8A4;
+ /** HDR system access failure. */
+ public static final int HDR_ACCESS_FAILURE = 0x8A5;
+ /**
+ * P_rev supported by 1 base station is less than 6, which is not supported for a 1X data call.
+ * The UE must be in the footprint of BS which has p_rev >= 6 to support this SO33 call.
+ */
+ public static final int UNSUPPORTED_1X_PREV = 0x8A6;
+ /** Client ended the data call. */
+ public static final int LOCAL_END = 0x8A7;
+ /** Device has no service. */
+ public static final int NO_SERVICE = 0x8A8;
+ /** Device lost the system due to fade. */
+ public static final int FADE = 0x8A9;
+ /** Receiving a release from the base station with no reason. */
+ public static final int NORMAL_RELEASE = 0x8AA;
+ /** Access attempt is already in progress. */
+ public static final int ACCESS_ATTEMPT_ALREADY_IN_PROGRESS = 0x8AB;
+ /** Device is in the process of redirecting or handing off to a different target system. */
+ public static final int REDIRECTION_OR_HANDOFF_IN_PROGRESS = 0x8AC;
+ /** Device is operating in Emergency mode. */
+ public static final int EMERGENCY_MODE = 0x8AD;
+ /** Device is in use (e.g., voice call). */
+ public static final int PHONE_IN_USE = 0x8AE;
+ /**
+ * Device operational mode is different from the mode requested in the traffic channel bring up.
+ */
+ public static final int INVALID_MODE = 0x8AF;
+ /** SIM was marked by the network as invalid for the circuit and/or packet service domain. */
+ public static final int INVALID_SIM_STATE = 0x8B0;
+ /** There is no co-located HDR. */
+ public static final int NO_COLLOCATED_HDR = 0x8B1;
+ /** UE is entering power save mode. */
+ public static final int UE_IS_ENTERING_POWERSAVE_MODE = 0x8B2;
+ /** Dual switch from single standby to dual standby is in progress. */
+ public static final int DUAL_SWITCH = 0x8B3;
+ /**
+ * Data call bring up fails in the PPP setup due to a timeout.
+ * (e.g., an LCP conf ack was not received from the network)
+ */
+ public static final int PPP_TIMEOUT = 0x8B4;
+ /**
+ * Data call bring up fails in the PPP setup due to an authorization failure.
+ * (e.g., authorization is required, but not negotiated with the network during an LCP phase)
+ */
+ public static final int PPP_AUTH_FAILURE = 0x8B5;
+ /** Data call bring up fails in the PPP setup due to an option mismatch. */
+ public static final int PPP_OPTION_MISMATCH = 0x8B6;
+ /** Data call bring up fails in the PPP setup due to a PAP failure. */
+ public static final int PPP_PAP_FAILURE = 0x8B7;
+ /** Data call bring up fails in the PPP setup due to a CHAP failure. */
+ public static final int PPP_CHAP_FAILURE = 0x8B8;
+ /**
+ * Data call bring up fails in the PPP setup because the PPP is in the process of cleaning the
+ * previous PPP session.
+ */
+ public static final int PPP_CLOSE_IN_PROGRESS = 0x8B9;
+ /**
+ * IPv6 interface bring up fails because the network provided only the IPv4 address for the
+ * upcoming PDN permanent client can reattempt a IPv6 call bring up after the IPv4 interface is
+ * also brought down. However, there is no guarantee that the network will provide a IPv6
+ * address.
+ */
+ public static final int LIMITED_TO_IPV4 = 0x8BA;
+ /**
+ * IPv4 interface bring up fails because the network provided only the IPv6 address for the
+ * upcoming PDN permanent client can reattempt a IPv4 call bring up after the IPv6 interface is
+ * also brought down. However there is no guarantee that the network will provide a IPv4
+ * address.
+ */
+ public static final int LIMITED_TO_IPV6 = 0x8BB;
+ /** Data call bring up fails in the VSNCP phase due to a VSNCP timeout error. */
+ public static final int VSNCP_TIMEOUT = 0x8BC;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a general error. It's used when there is
+ * no other specific error code available to report the failure.
+ */
+ public static final int VSNCP_GEN_ERROR = 0x8BD;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request because the requested APN is unauthorized.
+ */
+ public static final int VSNCP_APN_UNATHORIZED = 0x8BE;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request because the PDN limit has been exceeded.
+ */
+ public static final int VSNCP_PDN_LIMIT_EXCEEDED = 0x8BF;
+ /**
+ * Data call bring up fails in the VSNCP phase due to the network rejected the VSNCP
+ * configuration request due to no PDN gateway address.
+ */
+ public static final int VSNCP_NO_PDN_GATEWAY_ADDRESS = 0x8C0;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request because the PDN gateway is unreachable.
+ */
+ public static final int VSNCP_PDN_GATEWAY_UNREACHABLE = 0x8C1;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request due to a PDN gateway reject.
+ */
+ public static final int VSNCP_PDN_GATEWAY_REJECT = 0x8C2;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request with the reason of insufficient parameter.
+ */
+ public static final int VSNCP_INSUFFICIENT_PARAMETERS = 0x8C3;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request with the reason of resource unavailable.
+ */
+ public static final int VSNCP_RESOURCE_UNAVAILABLE = 0x8C4;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request with the reason of administratively prohibited at the HSGW.
+ */
+ public static final int VSNCP_ADMINISTRATIVELY_PROHIBITED = 0x8C5;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of PDN ID in use, or
+ * all existing PDNs are brought down with this end reason because one of the PDN bring up was
+ * rejected by the network with the reason of PDN ID in use.
+ */
+ public static final int VSNCP_PDN_ID_IN_USE = 0x8C6;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request for the reason of subscriber limitation.
+ */
+ public static final int VSNCP_SUBSCRIBER_LIMITATION = 0x8C7;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request because the PDN exists for this APN.
+ */
+ public static final int VSNCP_PDN_EXISTS_FOR_THIS_APN = 0x8C8;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request with reconnect to this PDN not allowed, or an active data call is
+ * terminated by the network because reconnection to this PDN is not allowed. Upon receiving
+ * this error code from the network, the modem infinitely throttles the PDN until the next
+ * power cycle.
+ */
+ public static final int VSNCP_RECONNECT_NOT_ALLOWED = 0x8C9;
+ /** Device failure to obtain the prefix from the network. */
+ public static final int IPV6_PREFIX_UNAVAILABLE = 0x8CA;
+ /** System preference change back to SRAT during handoff */
+ public static final int HANDOFF_PREFERENCE_CHANGED = 0x8CB;
// OEM sepecific error codes. To be used by OEMs when they don't
// want to reveal error code which would be replaced by ERROR_UNSPECIFIED
@@ -199,6 +961,13 @@
/** @hide */
public static final int RESET_BY_FRAMEWORK = 0x10005;
+ /**
+ * Data handover failed.
+ *
+ * @hide
+ */
+ public static final int HANDOVER_FAILED = 0x10006;
+
/** @hide */
@IntDef(value = {
NONE,
@@ -226,12 +995,18 @@
FILTER_SEMANTIC_ERROR,
FILTER_SYTAX_ERROR,
PDP_WITHOUT_ACTIVE_TFT,
+ ACTIVATION_REJECTED_BCM_VIOLATION,
ONLY_IPV4_ALLOWED,
ONLY_IPV6_ALLOWED,
ONLY_SINGLE_BEARER_ALLOWED,
ESM_INFO_NOT_RECEIVED,
PDN_CONN_DOES_NOT_EXIST,
MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED,
+ COLLISION_WITH_NETWORK_INITIATED_REQUEST,
+ ONLY_IPV4V6_ALLOWED,
+ ONLY_NON_IP_ALLOWED,
+ UNSUPPORTED_QCI_VALUE,
+ BEARER_HANDLING_NOT_SUPPORTED,
ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED,
UNSUPPORTED_APN_IN_CURRENT_PLMN,
INVALID_TRANSACTION_ID,
@@ -242,7 +1017,7 @@
UNKNOWN_INFO_ELEMENT,
CONDITIONAL_IE_ERROR,
MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE,
- PROTOCOL_ERRORS, /* no retry */
+ PROTOCOL_ERRORS,
APN_TYPE_CONFLICT,
INVALID_PCSCF_ADDR,
INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN,
@@ -254,6 +1029,262 @@
IFACE_AND_POL_FAMILY_MISMATCH,
EMM_ACCESS_BARRED_INFINITE_RETRY,
AUTH_FAILURE_ON_EMERGENCY_CALL,
+ INVALID_DNS_ADDR,
+ INVALID_PCSCF_OR_DNS_ADDRESS,
+ CALL_PREEMPT_BY_EMERGENCY_APN,
+ UE_INITIATED_DETACH_OR_DISCONNECT,
+ MIP_FA_REASON_UNSPECIFIED,
+ MIP_FA_ADMIN_PROHIBITED,
+ MIP_FA_INSUFFICIENT_RESOURCES,
+ MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE,
+ MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE,
+ MIP_FA_REQUESTED_LIFETIME_TOO_LONG,
+ MIP_FA_MALFORMED_REQUEST,
+ MIP_FA_MALFORMED_REPLY,
+ MIP_FA_ENCAPSULATION_UNAVAILABLE,
+ MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE,
+ MIP_FA_REVERSE_TUNNEL_UNAVAILABLE,
+ MIP_FA_REVERSE_TUNNEL_IS_MANDATORY,
+ MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED,
+ MIP_FA_MISSING_NAI,
+ MIP_FA_MISSING_HOME_AGENT,
+ MIP_FA_MISSING_HOME_ADDRESS,
+ MIP_FA_UNKNOWN_CHALLENGE,
+ MIP_FA_MISSING_CHALLENGE,
+ MIP_FA_STALE_CHALLENGE,
+ MIP_HA_REASON_UNSPECIFIED,
+ MIP_HA_ADMIN_PROHIBITED,
+ MIP_HA_INSUFFICIENT_RESOURCES,
+ MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE,
+ MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE,
+ MIP_HA_REGISTRATION_ID_MISMATCH,
+ MIP_HA_MALFORMED_REQUEST,
+ MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS,
+ MIP_HA_REVERSE_TUNNEL_UNAVAILABLE,
+ MIP_HA_REVERSE_TUNNEL_IS_MANDATORY,
+ MIP_HA_ENCAPSULATION_UNAVAILABLE,
+ CLOSE_IN_PROGRESS,
+ NETWORK_INITIATED_TERMINATION,
+ MODEM_APP_PREEMPTED,
+ PDN_IPV4_CALL_DISALLOWED,
+ PDN_IPV4_CALL_THROTTLED,
+ PDN_IPV6_CALL_DISALLOWED,
+ PDN_IPV6_CALL_THROTTLED,
+ MODEM_RESTART,
+ PDP_PPP_NOT_SUPPORTED,
+ UNPREFERRED_RAT,
+ PHYSICAL_LINK_CLOSE_IN_PROGRESS,
+ APN_PENDING_HANDOVER,
+ PROFILE_BEARER_INCOMPATIBLE,
+ SIM_CARD_CHANGED,
+ LOW_POWER_MODE_OR_POWERING_DOWN,
+ APN_DISABLED,
+ MAX_PPP_INACTIVITY_TIMER_EXPIRED,
+ IPV6_ADDRESS_TRANSFER_FAILED,
+ TRAT_SWAP_FAILED,
+ EHRPD_TO_HRPD_FALLBACK,
+ MIP_CONFIG_FAILURE,
+ PDN_INACTIVITY_TIMER_EXPIRED,
+ MAX_IPV4_CONNECTIONS,
+ MAX_IPV6_CONNECTIONS,
+ APN_MISMATCH,
+ IP_VERSION_MISMATCH,
+ DUN_CALL_DISALLOWED,
+ INTERNAL_EPC_NONEPC_TRANSITION,
+ INTERFACE_IN_USE,
+ APN_DISALLOWED_ON_ROAMING,
+ APN_PARAMETERS_CHANGED,
+ NULL_APN_DISALLOWED,
+ THERMAL_MITIGATION,
+ DATA_SETTINGS_DISABLED,
+ DATA_ROAMING_SETTINGS_DISABLED,
+ DDS_SWITCHED,
+ FORBIDDEN_APN_NAME,
+ DDS_SWITCH_IN_PROGRESS,
+ CALL_DISALLOWED_IN_ROAMING,
+ NON_IP_NOT_SUPPORTED,
+ PDN_NON_IP_CALL_THROTTLED,
+ PDN_NON_IP_CALL_DISALLOWED,
+ CDMA_LOCK,
+ CDMA_INTERCEPT,
+ CDMA_REORDER,
+ CDMA_RELEASE_DUE_TO_SO_REJECTION,
+ CDMA_INCOMING_CALL,
+ CDMA_ALERT_STOP,
+ CHANNEL_ACQUISITION_FAILURE,
+ MAX_ACCESS_PROBE,
+ CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION,
+ NO_RESPONSE_FROM_BASE_STATION,
+ REJECTED_BY_BASE_STATION,
+ CONCURRENT_SERVICES_INCOMPATIBLE,
+ NO_CDMA_SERVICE,
+ RUIM_NOT_PRESENT,
+ CDMA_RETRY_ORDER,
+ ACCESS_BLOCK,
+ ACCESS_BLOCK_ALL,
+ IS707B_MAX_ACCESS_PROBES,
+ THERMAL_EMERGENCY,
+ CONCURRENT_SERVICES_NOT_ALLOWED,
+ INCOMING_CALL_REJECTED,
+ NO_SERVICE_ON_GATEWAY,
+ NO_GPRS_CONTEXT,
+ ILLEGAL_MS,
+ ILLEGAL_ME,
+ GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED,
+ GPRS_SERVICES_NOT_ALLOWED,
+ MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK,
+ IMPLICITLY_DETACHED,
+ PLMN_NOT_ALLOWED,
+ LOCATION_AREA_NOT_ALLOWED,
+ GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN,
+ PDP_DUPLICATE,
+ UE_RAT_CHANGE,
+ CONGESTION,
+ NO_PDP_CONTEXT_ACTIVATED,
+ ACCESS_CLASS_DSAC_REJECTION,
+ PDP_ACTIVATE_MAX_RETRY_FAILED,
+ RADIO_ACCESS_BEARER_FAILURE,
+ ESM_UNKNOWN_EPS_BEARER_CONTEXT,
+ DRB_RELEASED_BY_RRC,
+ CONNECTION_RELEASED,
+ EMM_DETACHED,
+ EMM_ATTACH_FAILED,
+ EMM_ATTACH_STARTED,
+ LTE_NAS_SERVICE_REQUEST_FAILED,
+ DUPLICATE_BEARER_ID,
+ ESM_COLLISION_SCENARIOS,
+ ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK,
+ ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER,
+ ESM_BAD_OTA_MESSAGE,
+ ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL,
+ ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT,
+ DS_EXPLICIT_DEACTIVATION,
+ ESM_LOCAL_CAUSE_NONE,
+ LTE_THROTTLING_NOT_REQUIRED,
+ ACCESS_CONTROL_LIST_CHECK_FAILURE,
+ SERVICE_NOT_ALLOWED_ON_PLMN,
+ EMM_T3417_EXPIRED,
+ EMM_T3417_EXT_EXPIRED,
+ RRC_UPLINK_DATA_TRANSMISSION_FAILURE,
+ RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER,
+ RRC_UPLINK_CONNECTION_RELEASE,
+ RRC_UPLINK_RADIO_LINK_FAILURE,
+ RRC_UPLINK_ERROR_REQUEST_FROM_NAS,
+ RRC_CONNECTION_ACCESS_STRATUM_FAILURE,
+ RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS,
+ RRC_CONNECTION_ACCESS_BARRED,
+ RRC_CONNECTION_CELL_RESELECTION,
+ RRC_CONNECTION_CONFIG_FAILURE,
+ RRC_CONNECTION_TIMER_EXPIRED,
+ RRC_CONNECTION_LINK_FAILURE,
+ RRC_CONNECTION_CELL_NOT_CAMPED,
+ RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE,
+ RRC_CONNECTION_REJECT_BY_NETWORK,
+ RRC_CONNECTION_NORMAL_RELEASE,
+ RRC_CONNECTION_RADIO_LINK_FAILURE,
+ RRC_CONNECTION_REESTABLISHMENT_FAILURE,
+ RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER,
+ RRC_CONNECTION_ABORT_REQUEST,
+ RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR,
+ NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH,
+ NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH,
+ ESM_PROCEDURE_TIME_OUT,
+ INVALID_CONNECTION_ID,
+ MAXIMIUM_NSAPIS_EXCEEDED,
+ INVALID_PRIMARY_NSAPI,
+ CANNOT_ENCODE_OTA_MESSAGE,
+ RADIO_ACCESS_BEARER_SETUP_FAILURE,
+ PDP_ESTABLISH_TIMEOUT_EXPIRED,
+ PDP_MODIFY_TIMEOUT_EXPIRED,
+ PDP_INACTIVE_TIMEOUT_EXPIRED,
+ PDP_LOWERLAYER_ERROR,
+ PDP_MODIFY_COLLISION,
+ MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED,
+ NAS_REQUEST_REJECTED_BY_NETWORK,
+ RRC_CONNECTION_INVALID_REQUEST,
+ RRC_CONNECTION_TRACKING_AREA_ID_CHANGED,
+ RRC_CONNECTION_RF_UNAVAILABLE,
+ RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE,
+ RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE,
+ RRC_CONNECTION_ABORTED_AFTER_HANDOVER,
+ RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE,
+ RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE,
+ IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER,
+ IMEI_NOT_ACCEPTED,
+ EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED,
+ EPS_SERVICES_NOT_ALLOWED_IN_PLMN,
+ MSC_TEMPORARILY_NOT_REACHABLE,
+ CS_DOMAIN_NOT_AVAILABLE,
+ ESM_FAILURE,
+ MAC_FAILURE,
+ SYNCHRONIZATION_FAILURE,
+ UE_SECURITY_CAPABILITIES_MISMATCH,
+ SECURITY_MODE_REJECTED,
+ UNACCEPTABLE_NON_EPS_AUTHENTICATION,
+ CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED,
+ NO_EPS_BEARER_CONTEXT_ACTIVATED,
+ INVALID_EMM_STATE,
+ NAS_LAYER_FAILURE,
+ MULTIPLE_PDP_CALL_NOT_ALLOWED,
+ EMBMS_NOT_ENABLED,
+ IRAT_HANDOVER_FAILED,
+ EMBMS_REGULAR_DEACTIVATION,
+ TEST_LOOPBACK_REGULAR_DEACTIVATION,
+ LOWER_LAYER_REGISTRATION_FAILURE,
+ DATA_PLAN_EXPIRED,
+ UMTS_HANDOVER_TO_IWLAN,
+ EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY,
+ EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE,
+ EVDO_HDR_CHANGED,
+ EVDO_HDR_EXITED,
+ EVDO_HDR_NO_SESSION,
+ EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL,
+ EVDO_HDR_CONNECTION_SETUP_TIMEOUT,
+ FAILED_TO_ACQUIRE_COLOCATED_HDR,
+ OTASP_COMMIT_IN_PROGRESS,
+ NO_HYBRID_HDR_SERVICE,
+ HDR_NO_LOCK_GRANTED,
+ DBM_OR_SMS_IN_PROGRESS,
+ HDR_FADE,
+ HDR_ACCESS_FAILURE,
+ UNSUPPORTED_1X_PREV,
+ LOCAL_END,
+ NO_SERVICE,
+ FADE,
+ NORMAL_RELEASE,
+ ACCESS_ATTEMPT_ALREADY_IN_PROGRESS,
+ REDIRECTION_OR_HANDOFF_IN_PROGRESS,
+ EMERGENCY_MODE,
+ PHONE_IN_USE,
+ INVALID_MODE,
+ INVALID_SIM_STATE,
+ NO_COLLOCATED_HDR,
+ UE_IS_ENTERING_POWERSAVE_MODE,
+ DUAL_SWITCH,
+ PPP_TIMEOUT,
+ PPP_AUTH_FAILURE,
+ PPP_OPTION_MISMATCH,
+ PPP_PAP_FAILURE,
+ PPP_CHAP_FAILURE,
+ PPP_CLOSE_IN_PROGRESS,
+ LIMITED_TO_IPV4,
+ LIMITED_TO_IPV6,
+ VSNCP_TIMEOUT,
+ VSNCP_GEN_ERROR,
+ VSNCP_APN_UNATHORIZED,
+ VSNCP_PDN_LIMIT_EXCEEDED,
+ VSNCP_NO_PDN_GATEWAY_ADDRESS,
+ VSNCP_PDN_GATEWAY_UNREACHABLE,
+ VSNCP_PDN_GATEWAY_REJECT,
+ VSNCP_INSUFFICIENT_PARAMETERS,
+ VSNCP_RESOURCE_UNAVAILABLE,
+ VSNCP_ADMINISTRATIVELY_PROHIBITED,
+ VSNCP_PDN_ID_IN_USE,
+ VSNCP_SUBSCRIBER_LIMITATION,
+ VSNCP_PDN_EXISTS_FOR_THIS_APN,
+ VSNCP_RECONNECT_NOT_ALLOWED,
+ IPV6_PREFIX_UNAVAILABLE,
+ HANDOFF_PREFERENCE_CHANGED,
OEM_DCFAILCAUSE_1,
OEM_DCFAILCAUSE_2,
OEM_DCFAILCAUSE_3,
@@ -317,6 +1348,7 @@
sFailCauseMap.put(FILTER_SEMANTIC_ERROR, "FILTER_SEMANTIC_ERROR");
sFailCauseMap.put(FILTER_SYTAX_ERROR, "FILTER_SYTAX_ERROR");
sFailCauseMap.put(PDP_WITHOUT_ACTIVE_TFT, "PDP_WITHOUT_ACTIVE_TFT");
+ sFailCauseMap.put(ACTIVATION_REJECTED_BCM_VIOLATION, "ACTIVATION_REJECTED_BCM_VIOLATION");
sFailCauseMap.put(ONLY_IPV4_ALLOWED, "ONLY_IPV4_ALLOWED");
sFailCauseMap.put(ONLY_IPV6_ALLOWED, "ONLY_IPV6_ALLOWED");
sFailCauseMap.put(ONLY_SINGLE_BEARER_ALLOWED, "ONLY_SINGLE_BEARER_ALLOWED");
@@ -324,6 +1356,12 @@
sFailCauseMap.put(PDN_CONN_DOES_NOT_EXIST, "PDN_CONN_DOES_NOT_EXIST");
sFailCauseMap.put(MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED,
"MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED");
+ sFailCauseMap.put(COLLISION_WITH_NETWORK_INITIATED_REQUEST,
+ "COLLISION_WITH_NETWORK_INITIATED_REQUEST");
+ sFailCauseMap.put(ONLY_IPV4V6_ALLOWED, "ONLY_IPV4V6_ALLOWED");
+ sFailCauseMap.put(ONLY_NON_IP_ALLOWED, "ONLY_NON_IP_ALLOWED");
+ sFailCauseMap.put(UNSUPPORTED_QCI_VALUE, "UNSUPPORTED_QCI_VALUE");
+ sFailCauseMap.put(BEARER_HANDLING_NOT_SUPPORTED, "BEARER_HANDLING_NOT_SUPPORTED");
sFailCauseMap.put(ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED,
"ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED");
sFailCauseMap.put(UNSUPPORTED_APN_IN_CURRENT_PLMN,
@@ -353,6 +1391,301 @@
"EMM_ACCESS_BARRED_INFINITE_RETRY");
sFailCauseMap.put(AUTH_FAILURE_ON_EMERGENCY_CALL,
"AUTH_FAILURE_ON_EMERGENCY_CALL");
+ sFailCauseMap.put(INVALID_DNS_ADDR, "INVALID_DNS_ADDR");
+ sFailCauseMap.put(INVALID_PCSCF_OR_DNS_ADDRESS, "INVALID_PCSCF_OR_DNS_ADDRESS");
+ sFailCauseMap.put(CALL_PREEMPT_BY_EMERGENCY_APN, "CALL_PREEMPT_BY_EMERGENCY_APN");
+ sFailCauseMap.put(UE_INITIATED_DETACH_OR_DISCONNECT, "UE_INITIATED_DETACH_OR_DISCONNECT");
+ sFailCauseMap.put(MIP_FA_REASON_UNSPECIFIED, "MIP_FA_REASON_UNSPECIFIED");
+ sFailCauseMap.put(MIP_FA_ADMIN_PROHIBITED, "MIP_FA_ADMIN_PROHIBITED");
+ sFailCauseMap.put(MIP_FA_INSUFFICIENT_RESOURCES, "MIP_FA_INSUFFICIENT_RESOURCES");
+ sFailCauseMap.put(MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE,
+ "MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE");
+ sFailCauseMap.put(MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE,
+ "MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE");
+ sFailCauseMap.put(MIP_FA_REQUESTED_LIFETIME_TOO_LONG, "MIP_FA_REQUESTED_LIFETIME_TOO_LONG");
+ sFailCauseMap.put(MIP_FA_MALFORMED_REQUEST, "MIP_FA_MALFORMED_REQUEST");
+ sFailCauseMap.put(MIP_FA_MALFORMED_REPLY, "MIP_FA_MALFORMED_REPLY");
+ sFailCauseMap.put(MIP_FA_ENCAPSULATION_UNAVAILABLE, "MIP_FA_ENCAPSULATION_UNAVAILABLE");
+ sFailCauseMap.put(MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE,
+ "MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE");
+ sFailCauseMap.put(MIP_FA_REVERSE_TUNNEL_UNAVAILABLE, "MIP_FA_REVERSE_TUNNEL_UNAVAILABLE");
+ sFailCauseMap.put(MIP_FA_REVERSE_TUNNEL_IS_MANDATORY, "MIP_FA_REVERSE_TUNNEL_IS_MANDATORY");
+ sFailCauseMap.put(MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED,
+ "MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED");
+ sFailCauseMap.put(MIP_FA_MISSING_NAI, "MIP_FA_MISSING_NAI");
+ sFailCauseMap.put(MIP_FA_MISSING_HOME_AGENT, "MIP_FA_MISSING_HOME_AGENT");
+ sFailCauseMap.put(MIP_FA_MISSING_HOME_ADDRESS, "MIP_FA_MISSING_HOME_ADDRESS");
+ sFailCauseMap.put(MIP_FA_UNKNOWN_CHALLENGE, "MIP_FA_UNKNOWN_CHALLENGE");
+ sFailCauseMap.put(MIP_FA_MISSING_CHALLENGE, "MIP_FA_MISSING_CHALLENGE");
+ sFailCauseMap.put(MIP_FA_STALE_CHALLENGE, "MIP_FA_STALE_CHALLENGE");
+ sFailCauseMap.put(MIP_HA_REASON_UNSPECIFIED, "MIP_HA_REASON_UNSPECIFIED");
+ sFailCauseMap.put(MIP_HA_ADMIN_PROHIBITED, "MIP_HA_ADMIN_PROHIBITED");
+ sFailCauseMap.put(MIP_HA_INSUFFICIENT_RESOURCES, "MIP_HA_INSUFFICIENT_RESOURCES");
+ sFailCauseMap.put(MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE,
+ "MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE");
+ sFailCauseMap.put(MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE,
+ "MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE");
+ sFailCauseMap.put(MIP_HA_REGISTRATION_ID_MISMATCH, "MIP_HA_REGISTRATION_ID_MISMATCH");
+ sFailCauseMap.put(MIP_HA_MALFORMED_REQUEST, "MIP_HA_MALFORMED_REQUEST");
+ sFailCauseMap.put(MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS, "MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS");
+ sFailCauseMap.put(MIP_HA_REVERSE_TUNNEL_UNAVAILABLE, "MIP_HA_REVERSE_TUNNEL_UNAVAILABLE");
+ sFailCauseMap.put(MIP_HA_REVERSE_TUNNEL_IS_MANDATORY, "MIP_HA_REVERSE_TUNNEL_IS_MANDATORY");
+ sFailCauseMap.put(MIP_HA_ENCAPSULATION_UNAVAILABLE, "MIP_HA_ENCAPSULATION_UNAVAILABLE");
+ sFailCauseMap.put(CLOSE_IN_PROGRESS, "CLOSE_IN_PROGRESS");
+ sFailCauseMap.put(NETWORK_INITIATED_TERMINATION, "NETWORK_INITIATED_TERMINATION");
+ sFailCauseMap.put(MODEM_APP_PREEMPTED, "MODEM_APP_PREEMPTED");
+ sFailCauseMap.put(PDN_IPV4_CALL_DISALLOWED, "PDN_IPV4_CALL_DISALLOWED");
+ sFailCauseMap.put(PDN_IPV4_CALL_THROTTLED, "PDN_IPV4_CALL_THROTTLED");
+ sFailCauseMap.put(PDN_IPV6_CALL_DISALLOWED, "PDN_IPV6_CALL_DISALLOWED");
+ sFailCauseMap.put(PDN_IPV6_CALL_THROTTLED, "PDN_IPV6_CALL_THROTTLED");
+ sFailCauseMap.put(MODEM_RESTART, "MODEM_RESTART");
+ sFailCauseMap.put(PDP_PPP_NOT_SUPPORTED, "PDP_PPP_NOT_SUPPORTED");
+ sFailCauseMap.put(UNPREFERRED_RAT, "UNPREFERRED_RAT");
+ sFailCauseMap.put(PHYSICAL_LINK_CLOSE_IN_PROGRESS, "PHYSICAL_LINK_CLOSE_IN_PROGRESS");
+ sFailCauseMap.put(APN_PENDING_HANDOVER, "APN_PENDING_HANDOVER");
+ sFailCauseMap.put(PROFILE_BEARER_INCOMPATIBLE, "PROFILE_BEARER_INCOMPATIBLE");
+ sFailCauseMap.put(SIM_CARD_CHANGED, "SIM_CARD_CHANGED");
+ sFailCauseMap.put(LOW_POWER_MODE_OR_POWERING_DOWN, "LOW_POWER_MODE_OR_POWERING_DOWN");
+ sFailCauseMap.put(APN_DISABLED, "APN_DISABLED");
+ sFailCauseMap.put(MAX_PPP_INACTIVITY_TIMER_EXPIRED, "MAX_PPP_INACTIVITY_TIMER_EXPIRED");
+ sFailCauseMap.put(IPV6_ADDRESS_TRANSFER_FAILED, "IPV6_ADDRESS_TRANSFER_FAILED");
+ sFailCauseMap.put(TRAT_SWAP_FAILED, "TRAT_SWAP_FAILED");
+ sFailCauseMap.put(EHRPD_TO_HRPD_FALLBACK, "EHRPD_TO_HRPD_FALLBACK");
+ sFailCauseMap.put(MIP_CONFIG_FAILURE, "MIP_CONFIG_FAILURE");
+ sFailCauseMap.put(PDN_INACTIVITY_TIMER_EXPIRED, "PDN_INACTIVITY_TIMER_EXPIRED");
+ sFailCauseMap.put(MAX_IPV4_CONNECTIONS, "MAX_IPV4_CONNECTIONS");
+ sFailCauseMap.put(MAX_IPV6_CONNECTIONS, "MAX_IPV6_CONNECTIONS");
+ sFailCauseMap.put(APN_MISMATCH, "APN_MISMATCH");
+ sFailCauseMap.put(IP_VERSION_MISMATCH, "IP_VERSION_MISMATCH");
+ sFailCauseMap.put(DUN_CALL_DISALLOWED, "DUN_CALL_DISALLOWED");
+ sFailCauseMap.put(INTERNAL_EPC_NONEPC_TRANSITION, "INTERNAL_EPC_NONEPC_TRANSITION");
+ sFailCauseMap.put(INTERFACE_IN_USE, "INTERFACE_IN_USE");
+ sFailCauseMap.put(APN_DISALLOWED_ON_ROAMING, "APN_DISALLOWED_ON_ROAMING");
+ sFailCauseMap.put(APN_PARAMETERS_CHANGED, "APN_PARAMETERS_CHANGED");
+ sFailCauseMap.put(NULL_APN_DISALLOWED, "NULL_APN_DISALLOWED");
+ sFailCauseMap.put(THERMAL_MITIGATION, "THERMAL_MITIGATION");
+ sFailCauseMap.put(DATA_SETTINGS_DISABLED, "DATA_SETTINGS_DISABLED");
+ sFailCauseMap.put(DATA_ROAMING_SETTINGS_DISABLED, "DATA_ROAMING_SETTINGS_DISABLED");
+ sFailCauseMap.put(DDS_SWITCHED, "DDS_SWITCHED");
+ sFailCauseMap.put(FORBIDDEN_APN_NAME, "FORBIDDEN_APN_NAME");
+ sFailCauseMap.put(DDS_SWITCH_IN_PROGRESS, "DDS_SWITCH_IN_PROGRESS");
+ sFailCauseMap.put(CALL_DISALLOWED_IN_ROAMING, "CALL_DISALLOWED_IN_ROAMING");
+ sFailCauseMap.put(NON_IP_NOT_SUPPORTED, "NON_IP_NOT_SUPPORTED");
+ sFailCauseMap.put(PDN_NON_IP_CALL_THROTTLED, "PDN_NON_IP_CALL_THROTTLED");
+ sFailCauseMap.put(PDN_NON_IP_CALL_DISALLOWED, "PDN_NON_IP_CALL_DISALLOWED");
+ sFailCauseMap.put(CDMA_LOCK, "CDMA_LOCK");
+ sFailCauseMap.put(CDMA_INTERCEPT, "CDMA_INTERCEPT");
+ sFailCauseMap.put(CDMA_REORDER, "CDMA_REORDER");
+ sFailCauseMap.put(CDMA_RELEASE_DUE_TO_SO_REJECTION, "CDMA_RELEASE_DUE_TO_SO_REJECTION");
+ sFailCauseMap.put(CDMA_INCOMING_CALL, "CDMA_INCOMING_CALL");
+ sFailCauseMap.put(CDMA_ALERT_STOP, "CDMA_ALERT_STOP");
+ sFailCauseMap.put(CHANNEL_ACQUISITION_FAILURE, "CHANNEL_ACQUISITION_FAILURE");
+ sFailCauseMap.put(MAX_ACCESS_PROBE, "MAX_ACCESS_PROBE");
+ sFailCauseMap.put(CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION,
+ "CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION");
+ sFailCauseMap.put(NO_RESPONSE_FROM_BASE_STATION, "NO_RESPONSE_FROM_BASE_STATION");
+ sFailCauseMap.put(REJECTED_BY_BASE_STATION, "REJECTED_BY_BASE_STATION");
+ sFailCauseMap.put(CONCURRENT_SERVICES_INCOMPATIBLE, "CONCURRENT_SERVICES_INCOMPATIBLE");
+ sFailCauseMap.put(NO_CDMA_SERVICE, "NO_CDMA_SERVICE");
+ sFailCauseMap.put(RUIM_NOT_PRESENT, "RUIM_NOT_PRESENT");
+ sFailCauseMap.put(CDMA_RETRY_ORDER, "CDMA_RETRY_ORDER");
+ sFailCauseMap.put(ACCESS_BLOCK, "ACCESS_BLOCK");
+ sFailCauseMap.put(ACCESS_BLOCK_ALL, "ACCESS_BLOCK_ALL");
+ sFailCauseMap.put(IS707B_MAX_ACCESS_PROBES, "IS707B_MAX_ACCESS_PROBES");
+ sFailCauseMap.put(THERMAL_EMERGENCY, "THERMAL_EMERGENCY");
+ sFailCauseMap.put(CONCURRENT_SERVICES_NOT_ALLOWED, "CONCURRENT_SERVICES_NOT_ALLOWED");
+ sFailCauseMap.put(INCOMING_CALL_REJECTED, "INCOMING_CALL_REJECTED");
+ sFailCauseMap.put(NO_SERVICE_ON_GATEWAY, "NO_SERVICE_ON_GATEWAY");
+ sFailCauseMap.put(NO_GPRS_CONTEXT, "NO_GPRS_CONTEXT");
+ sFailCauseMap.put(ILLEGAL_MS, "ILLEGAL_MS");
+ sFailCauseMap.put(ILLEGAL_ME, "ILLEGAL_ME");
+ sFailCauseMap.put(GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED,
+ "GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED");
+ sFailCauseMap.put(GPRS_SERVICES_NOT_ALLOWED, "GPRS_SERVICES_NOT_ALLOWED");
+ sFailCauseMap.put(MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK,
+ "MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK");
+ sFailCauseMap.put(IMPLICITLY_DETACHED, "IMPLICITLY_DETACHED");
+ sFailCauseMap.put(PLMN_NOT_ALLOWED, "PLMN_NOT_ALLOWED");
+ sFailCauseMap.put(LOCATION_AREA_NOT_ALLOWED, "LOCATION_AREA_NOT_ALLOWED");
+ sFailCauseMap.put(GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN,
+ "GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN");
+ sFailCauseMap.put(PDP_DUPLICATE, "PDP_DUPLICATE");
+ sFailCauseMap.put(UE_RAT_CHANGE, "UE_RAT_CHANGE");
+ sFailCauseMap.put(CONGESTION, "CONGESTION");
+ sFailCauseMap.put(NO_PDP_CONTEXT_ACTIVATED, "NO_PDP_CONTEXT_ACTIVATED");
+ sFailCauseMap.put(ACCESS_CLASS_DSAC_REJECTION, "ACCESS_CLASS_DSAC_REJECTION");
+ sFailCauseMap.put(PDP_ACTIVATE_MAX_RETRY_FAILED, "PDP_ACTIVATE_MAX_RETRY_FAILED");
+ sFailCauseMap.put(RADIO_ACCESS_BEARER_FAILURE, "RADIO_ACCESS_BEARER_FAILURE");
+ sFailCauseMap.put(ESM_UNKNOWN_EPS_BEARER_CONTEXT, "ESM_UNKNOWN_EPS_BEARER_CONTEXT");
+ sFailCauseMap.put(DRB_RELEASED_BY_RRC, "DRB_RELEASED_BY_RRC");
+ sFailCauseMap.put(CONNECTION_RELEASED, "CONNECTION_RELEASED");
+ sFailCauseMap.put(EMM_DETACHED, "EMM_DETACHED");
+ sFailCauseMap.put(EMM_ATTACH_FAILED, "EMM_ATTACH_FAILED");
+ sFailCauseMap.put(EMM_ATTACH_STARTED, "EMM_ATTACH_STARTED");
+ sFailCauseMap.put(LTE_NAS_SERVICE_REQUEST_FAILED, "LTE_NAS_SERVICE_REQUEST_FAILED");
+ sFailCauseMap.put(DUPLICATE_BEARER_ID, "DUPLICATE_BEARER_ID");
+ sFailCauseMap.put(ESM_COLLISION_SCENARIOS, "ESM_COLLISION_SCENARIOS");
+ sFailCauseMap.put(ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK,
+ "ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK");
+ sFailCauseMap.put(ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER,
+ "ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER");
+ sFailCauseMap.put(ESM_BAD_OTA_MESSAGE, "ESM_BAD_OTA_MESSAGE");
+ sFailCauseMap.put(ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL,
+ "ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL");
+ sFailCauseMap.put(ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT,
+ "ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT");
+ sFailCauseMap.put(DS_EXPLICIT_DEACTIVATION, "DS_EXPLICIT_DEACTIVATION");
+ sFailCauseMap.put(ESM_LOCAL_CAUSE_NONE, "ESM_LOCAL_CAUSE_NONE");
+ sFailCauseMap.put(LTE_THROTTLING_NOT_REQUIRED, "LTE_THROTTLING_NOT_REQUIRED");
+ sFailCauseMap.put(ACCESS_CONTROL_LIST_CHECK_FAILURE,
+ "ACCESS_CONTROL_LIST_CHECK_FAILURE");
+ sFailCauseMap.put(SERVICE_NOT_ALLOWED_ON_PLMN, "SERVICE_NOT_ALLOWED_ON_PLMN");
+ sFailCauseMap.put(EMM_T3417_EXPIRED, "EMM_T3417_EXPIRED");
+ sFailCauseMap.put(EMM_T3417_EXT_EXPIRED, "EMM_T3417_EXT_EXPIRED");
+ sFailCauseMap.put(RRC_UPLINK_DATA_TRANSMISSION_FAILURE,
+ "RRC_UPLINK_DATA_TRANSMISSION_FAILURE");
+ sFailCauseMap.put(RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER,
+ "RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER");
+ sFailCauseMap.put(RRC_UPLINK_CONNECTION_RELEASE, "RRC_UPLINK_CONNECTION_RELEASE");
+ sFailCauseMap.put(RRC_UPLINK_RADIO_LINK_FAILURE, "RRC_UPLINK_RADIO_LINK_FAILURE");
+ sFailCauseMap.put(RRC_UPLINK_ERROR_REQUEST_FROM_NAS, "RRC_UPLINK_ERROR_REQUEST_FROM_NAS");
+ sFailCauseMap.put(RRC_CONNECTION_ACCESS_STRATUM_FAILURE,
+ "RRC_CONNECTION_ACCESS_STRATUM_FAILURE");
+ sFailCauseMap.put(RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS,
+ "RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS");
+ sFailCauseMap.put(RRC_CONNECTION_ACCESS_BARRED, "RRC_CONNECTION_ACCESS_BARRED");
+ sFailCauseMap.put(RRC_CONNECTION_CELL_RESELECTION, "RRC_CONNECTION_CELL_RESELECTION");
+ sFailCauseMap.put(RRC_CONNECTION_CONFIG_FAILURE, "RRC_CONNECTION_CONFIG_FAILURE");
+ sFailCauseMap.put(RRC_CONNECTION_TIMER_EXPIRED, "RRC_CONNECTION_TIMER_EXPIRED");
+ sFailCauseMap.put(RRC_CONNECTION_LINK_FAILURE, "RRC_CONNECTION_LINK_FAILURE");
+ sFailCauseMap.put(RRC_CONNECTION_CELL_NOT_CAMPED, "RRC_CONNECTION_CELL_NOT_CAMPED");
+ sFailCauseMap.put(RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE,
+ "RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE");
+ sFailCauseMap.put(RRC_CONNECTION_REJECT_BY_NETWORK, "RRC_CONNECTION_REJECT_BY_NETWORK");
+ sFailCauseMap.put(RRC_CONNECTION_NORMAL_RELEASE, "RRC_CONNECTION_NORMAL_RELEASE");
+ sFailCauseMap.put(RRC_CONNECTION_RADIO_LINK_FAILURE, "RRC_CONNECTION_RADIO_LINK_FAILURE");
+ sFailCauseMap.put(RRC_CONNECTION_REESTABLISHMENT_FAILURE,
+ "RRC_CONNECTION_REESTABLISHMENT_FAILURE");
+ sFailCauseMap.put(RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER,
+ "RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER");
+ sFailCauseMap.put(RRC_CONNECTION_ABORT_REQUEST, "RRC_CONNECTION_ABORT_REQUEST");
+ sFailCauseMap.put(RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR,
+ "RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR");
+ sFailCauseMap.put(NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH,
+ "NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH");
+ sFailCauseMap.put(NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH,
+ "NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH");
+ sFailCauseMap.put(ESM_PROCEDURE_TIME_OUT, "ESM_PROCEDURE_TIME_OUT");
+ sFailCauseMap.put(INVALID_CONNECTION_ID, "INVALID_CONNECTION_ID");
+ sFailCauseMap.put(MAXIMIUM_NSAPIS_EXCEEDED, "MAXIMIUM_NSAPIS_EXCEEDED");
+ sFailCauseMap.put(INVALID_PRIMARY_NSAPI, "INVALID_PRIMARY_NSAPI");
+ sFailCauseMap.put(CANNOT_ENCODE_OTA_MESSAGE, "CANNOT_ENCODE_OTA_MESSAGE");
+ sFailCauseMap.put(RADIO_ACCESS_BEARER_SETUP_FAILURE, "RADIO_ACCESS_BEARER_SETUP_FAILURE");
+ sFailCauseMap.put(PDP_ESTABLISH_TIMEOUT_EXPIRED, "PDP_ESTABLISH_TIMEOUT_EXPIRED");
+ sFailCauseMap.put(PDP_MODIFY_TIMEOUT_EXPIRED, "PDP_MODIFY_TIMEOUT_EXPIRED");
+ sFailCauseMap.put(PDP_INACTIVE_TIMEOUT_EXPIRED, "PDP_INACTIVE_TIMEOUT_EXPIRED");
+ sFailCauseMap.put(PDP_LOWERLAYER_ERROR, "PDP_LOWERLAYER_ERROR");
+ sFailCauseMap.put(PDP_MODIFY_COLLISION, "PDP_MODIFY_COLLISION");
+ sFailCauseMap.put(MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED,
+ "MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED");
+ sFailCauseMap.put(NAS_REQUEST_REJECTED_BY_NETWORK, "NAS_REQUEST_REJECTED_BY_NETWORK");
+ sFailCauseMap.put(RRC_CONNECTION_INVALID_REQUEST, "RRC_CONNECTION_INVALID_REQUEST");
+ sFailCauseMap.put(RRC_CONNECTION_TRACKING_AREA_ID_CHANGED,
+ "RRC_CONNECTION_TRACKING_AREA_ID_CHANGED");
+ sFailCauseMap.put(RRC_CONNECTION_RF_UNAVAILABLE, "RRC_CONNECTION_RF_UNAVAILABLE");
+ sFailCauseMap.put(RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE,
+ "RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE");
+ sFailCauseMap.put(RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE,
+ "RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE");
+ sFailCauseMap.put(RRC_CONNECTION_ABORTED_AFTER_HANDOVER,
+ "RRC_CONNECTION_ABORTED_AFTER_HANDOVER");
+ sFailCauseMap.put(RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE,
+ "RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE");
+ sFailCauseMap.put(RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE,
+ "RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE");
+ sFailCauseMap.put(IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER,
+ "IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER");
+ sFailCauseMap.put(IMEI_NOT_ACCEPTED, "IMEI_NOT_ACCEPTED");
+ sFailCauseMap.put(EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED,
+ "EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED");
+ sFailCauseMap.put(EPS_SERVICES_NOT_ALLOWED_IN_PLMN, "EPS_SERVICES_NOT_ALLOWED_IN_PLMN");
+ sFailCauseMap.put(MSC_TEMPORARILY_NOT_REACHABLE, "MSC_TEMPORARILY_NOT_REACHABLE");
+ sFailCauseMap.put(CS_DOMAIN_NOT_AVAILABLE, "CS_DOMAIN_NOT_AVAILABLE");
+ sFailCauseMap.put(ESM_FAILURE, "ESM_FAILURE");
+ sFailCauseMap.put(MAC_FAILURE, "MAC_FAILURE");
+ sFailCauseMap.put(SYNCHRONIZATION_FAILURE, "SYNCHRONIZATION_FAILURE");
+ sFailCauseMap.put(UE_SECURITY_CAPABILITIES_MISMATCH, "UE_SECURITY_CAPABILITIES_MISMATCH");
+ sFailCauseMap.put(SECURITY_MODE_REJECTED, "SECURITY_MODE_REJECTED");
+ sFailCauseMap.put(UNACCEPTABLE_NON_EPS_AUTHENTICATION,
+ "UNACCEPTABLE_NON_EPS_AUTHENTICATION");
+ sFailCauseMap.put(CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED,
+ "CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED");
+ sFailCauseMap.put(NO_EPS_BEARER_CONTEXT_ACTIVATED, "NO_EPS_BEARER_CONTEXT_ACTIVATED");
+ sFailCauseMap.put(INVALID_EMM_STATE, "INVALID_EMM_STATE");
+ sFailCauseMap.put(NAS_LAYER_FAILURE, "NAS_LAYER_FAILURE");
+ sFailCauseMap.put(MULTIPLE_PDP_CALL_NOT_ALLOWED, "MULTIPLE_PDP_CALL_NOT_ALLOWED");
+ sFailCauseMap.put(EMBMS_NOT_ENABLED, "EMBMS_NOT_ENABLED");
+ sFailCauseMap.put(IRAT_HANDOVER_FAILED, "IRAT_HANDOVER_FAILED");
+ sFailCauseMap.put(EMBMS_REGULAR_DEACTIVATION, "EMBMS_REGULAR_DEACTIVATION");
+ sFailCauseMap.put(TEST_LOOPBACK_REGULAR_DEACTIVATION, "TEST_LOOPBACK_REGULAR_DEACTIVATION");
+ sFailCauseMap.put(LOWER_LAYER_REGISTRATION_FAILURE, "LOWER_LAYER_REGISTRATION_FAILURE");
+ sFailCauseMap.put(DATA_PLAN_EXPIRED, "DATA_PLAN_EXPIRED");
+ sFailCauseMap.put(UMTS_HANDOVER_TO_IWLAN, "UMTS_HANDOVER_TO_IWLAN");
+ sFailCauseMap.put(EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY,
+ "EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY");
+ sFailCauseMap.put(EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE,
+ "EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE");
+ sFailCauseMap.put(EVDO_HDR_CHANGED, "EVDO_HDR_CHANGED");
+ sFailCauseMap.put(EVDO_HDR_EXITED, "EVDO_HDR_EXITED");
+ sFailCauseMap.put(EVDO_HDR_NO_SESSION, "EVDO_HDR_NO_SESSION");
+ sFailCauseMap.put(EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL,
+ "EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL");
+ sFailCauseMap.put(EVDO_HDR_CONNECTION_SETUP_TIMEOUT, "EVDO_HDR_CONNECTION_SETUP_TIMEOUT");
+ sFailCauseMap.put(FAILED_TO_ACQUIRE_COLOCATED_HDR, "FAILED_TO_ACQUIRE_COLOCATED_HDR");
+ sFailCauseMap.put(OTASP_COMMIT_IN_PROGRESS, "OTASP_COMMIT_IN_PROGRESS");
+ sFailCauseMap.put(NO_HYBRID_HDR_SERVICE, "NO_HYBRID_HDR_SERVICE");
+ sFailCauseMap.put(HDR_NO_LOCK_GRANTED, "HDR_NO_LOCK_GRANTED");
+ sFailCauseMap.put(DBM_OR_SMS_IN_PROGRESS, "DBM_OR_SMS_IN_PROGRESS");
+ sFailCauseMap.put(HDR_FADE, "HDR_FADE");
+ sFailCauseMap.put(HDR_ACCESS_FAILURE, "HDR_ACCESS_FAILURE");
+ sFailCauseMap.put(UNSUPPORTED_1X_PREV, "UNSUPPORTED_1X_PREV");
+ sFailCauseMap.put(LOCAL_END, "LOCAL_END");
+ sFailCauseMap.put(NO_SERVICE, "NO_SERVICE");
+ sFailCauseMap.put(FADE, "FADE");
+ sFailCauseMap.put(NORMAL_RELEASE, "NORMAL_RELEASE");
+ sFailCauseMap.put(ACCESS_ATTEMPT_ALREADY_IN_PROGRESS, "ACCESS_ATTEMPT_ALREADY_IN_PROGRESS");
+ sFailCauseMap.put(REDIRECTION_OR_HANDOFF_IN_PROGRESS, "REDIRECTION_OR_HANDOFF_IN_PROGRESS");
+ sFailCauseMap.put(EMERGENCY_MODE, "EMERGENCY_MODE");
+ sFailCauseMap.put(PHONE_IN_USE, "PHONE_IN_USE");
+ sFailCauseMap.put(INVALID_MODE, "INVALID_MODE");
+ sFailCauseMap.put(INVALID_SIM_STATE, "INVALID_SIM_STATE");
+ sFailCauseMap.put(NO_COLLOCATED_HDR, "NO_COLLOCATED_HDR");
+ sFailCauseMap.put(UE_IS_ENTERING_POWERSAVE_MODE, "UE_IS_ENTERING_POWERSAVE_MODE");
+ sFailCauseMap.put(DUAL_SWITCH, "DUAL_SWITCH");
+ sFailCauseMap.put(PPP_TIMEOUT, "PPP_TIMEOUT");
+ sFailCauseMap.put(PPP_AUTH_FAILURE, "PPP_AUTH_FAILURE");
+ sFailCauseMap.put(PPP_OPTION_MISMATCH, "PPP_OPTION_MISMATCH");
+ sFailCauseMap.put(PPP_PAP_FAILURE, "PPP_PAP_FAILURE");
+ sFailCauseMap.put(PPP_CHAP_FAILURE, "PPP_CHAP_FAILURE");
+ sFailCauseMap.put(PPP_CLOSE_IN_PROGRESS, "PPP_CLOSE_IN_PROGRESS");
+ sFailCauseMap.put(LIMITED_TO_IPV4, "LIMITED_TO_IPV4");
+ sFailCauseMap.put(LIMITED_TO_IPV6, "LIMITED_TO_IPV6");
+ sFailCauseMap.put(VSNCP_TIMEOUT, "VSNCP_TIMEOUT");
+ sFailCauseMap.put(VSNCP_GEN_ERROR, "VSNCP_GEN_ERROR");
+ sFailCauseMap.put(VSNCP_APN_UNATHORIZED, "VSNCP_APN_UNATHORIZED");
+ sFailCauseMap.put(VSNCP_PDN_LIMIT_EXCEEDED, "VSNCP_PDN_LIMIT_EXCEEDED");
+ sFailCauseMap.put(VSNCP_NO_PDN_GATEWAY_ADDRESS, "VSNCP_NO_PDN_GATEWAY_ADDRESS");
+ sFailCauseMap.put(VSNCP_PDN_GATEWAY_UNREACHABLE, "VSNCP_PDN_GATEWAY_UNREACHABLE");
+ sFailCauseMap.put(VSNCP_PDN_GATEWAY_REJECT, "VSNCP_PDN_GATEWAY_REJECT");
+ sFailCauseMap.put(VSNCP_INSUFFICIENT_PARAMETERS, "VSNCP_INSUFFICIENT_PARAMETERS");
+ sFailCauseMap.put(VSNCP_RESOURCE_UNAVAILABLE, "VSNCP_RESOURCE_UNAVAILABLE");
+ sFailCauseMap.put(VSNCP_ADMINISTRATIVELY_PROHIBITED, "VSNCP_ADMINISTRATIVELY_PROHIBITED");
+ sFailCauseMap.put(VSNCP_PDN_ID_IN_USE, "VSNCP_PDN_ID_IN_USE");
+ sFailCauseMap.put(VSNCP_SUBSCRIBER_LIMITATION, "VSNCP_SUBSCRIBER_LIMITATION");
+ sFailCauseMap.put(VSNCP_PDN_EXISTS_FOR_THIS_APN, "VSNCP_PDN_EXISTS_FOR_THIS_APN");
+ sFailCauseMap.put(VSNCP_RECONNECT_NOT_ALLOWED, "VSNCP_RECONNECT_NOT_ALLOWED");
+ sFailCauseMap.put(IPV6_PREFIX_UNAVAILABLE, "IPV6_PREFIX_UNAVAILABLE");
+ sFailCauseMap.put(HANDOFF_PREFERENCE_CHANGED, "HANDOFF_PREFERENCE_CHANGED");
sFailCauseMap.put(OEM_DCFAILCAUSE_1, "OEM_DCFAILCAUSE_1");
sFailCauseMap.put(OEM_DCFAILCAUSE_2, "OEM_DCFAILCAUSE_2");
sFailCauseMap.put(OEM_DCFAILCAUSE_3, "OEM_DCFAILCAUSE_3");
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index ad3ca6d..91375bc 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -292,14 +292,27 @@
* Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
*
* @return RSSI in ASU 0..31, 99, or UNAVAILABLE
+ *
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthGsm#getAsuLevel}.
+ * @see android.telephony#CellSignalStrengthGsm
+ * @see android.telephony.SignalStrength#getCellSignalStrengths
*/
+ @Deprecated
public int getGsmSignalStrength() {
return mGsm.getAsuLevel();
}
/**
* Get the GSM bit error rate (0-7, 99) as defined in TS 27.007 8.5
+ *
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthGsm#getBitErrorRate}.
+ *
+ * @see android.telephony#CellSignalStrengthGsm
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
*/
+ @Deprecated
public int getGsmBitErrorRate() {
return mGsm.getBitErrorRate();
}
@@ -308,14 +321,28 @@
* Get the CDMA RSSI value in dBm
*
* @return the CDMA RSSI value or {@link #INVALID} if invalid
+ *
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getCdmaDbm}.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
*/
+ @Deprecated
public int getCdmaDbm() {
return mCdma.getCdmaDbm();
}
/**
* Get the CDMA Ec/Io value in dB*10
+ *
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getCdmaEcio}.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
*/
+ @Deprecated
public int getCdmaEcio() {
return mCdma.getCdmaEcio();
}
@@ -324,51 +351,112 @@
* Get the EVDO RSSI value in dBm
*
* @return the EVDO RSSI value or {@link #INVALID} if invalid
+ *
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getEvdoDbm}.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
*/
+ @Deprecated
public int getEvdoDbm() {
return mCdma.getEvdoDbm();
}
/**
* Get the EVDO Ec/Io value in dB*10
+ *
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getEvdoEcio}.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
*/
+ @Deprecated
public int getEvdoEcio() {
return mCdma.getEvdoEcio();
}
/**
* Get the signal to noise ratio. Valid values are 0-8. 8 is the highest.
+ *
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getEvdoSnr}.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
*/
+ @Deprecated
public int getEvdoSnr() {
return mCdma.getEvdoSnr();
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getRssi}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteSignalStrength() {
return mLte.getRssi();
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getRsrp}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteRsrp() {
return mLte.getRsrp();
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getRsrq}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteRsrq() {
return mLte.getRsrq();
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getRssnr}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteRssnr() {
return mLte.getRssnr();
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getCqi}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteCqi() {
return mLte.getCqi();
}
@@ -391,11 +479,17 @@
}
/**
- * Get the signal level as an asu value between 0..31, 99 is unknown
+ * Get the signal level as an asu value with a range dependent on the underlying technology.
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrength#getAsuLevel}. Because the levels vary by technology,
+ * this method is misleading and should not be used.
+ * @see android.telephony#CellSignalStrength
+ * @see android.telephony.SignalStrength#getCellSignalStrengths
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getAsuLevel() {
return getPrimary().getAsuLevel();
}
@@ -403,9 +497,15 @@
/**
* Get the signal strength as dBm
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrength#getDbm()}. Because the levels vary by technology,
+ * this method is misleading and should not be used.
+ * @see android.telephony#CellSignalStrength
+ * @see android.telephony.SignalStrength#getCellSignalStrengths
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getDbm() {
return getPrimary().getDbm();
}
@@ -413,9 +513,15 @@
/**
* Get Gsm signal strength as dBm
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthGsm#getDbm}.
+ *
+ * @see android.telephony#CellSignalStrengthGsm
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getGsmDbm() {
return mGsm.getDbm();
}
@@ -423,9 +529,15 @@
/**
* Get gsm as level 0..4
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthGsm#getLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthGsm
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getGsmLevel() {
return mGsm.getLevel();
}
@@ -433,9 +545,15 @@
/**
* Get the gsm signal level as an asu value between 0..31, 99 is unknown
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthGsm#getAsuLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthGsm
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getGsmAsuLevel() {
return mGsm.getAsuLevel();
}
@@ -443,9 +561,15 @@
/**
* Get cdma as level 0..4
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getCdmaLevel() {
return mCdma.getLevel();
}
@@ -453,9 +577,17 @@
/**
* Get the cdma signal level as an asu value between 0..31, 99 is unknown
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getAsuLevel}. Since there is no definition of
+ * ASU for CDMA, the resultant value is Android-specific and is not recommended
+ * for use.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getCdmaAsuLevel() {
return mCdma.getAsuLevel();
}
@@ -463,9 +595,15 @@
/**
* Get Evdo as level 0..4
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getEvdoLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getEvdoLevel() {
return mCdma.getEvdoLevel();
}
@@ -473,9 +611,17 @@
/**
* Get the evdo signal level as an asu value between 0..31, 99 is unknown
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getEvdoAsuLevel}. Since there is no definition of
+ * ASU for EvDO, the resultant value is Android-specific and is not recommended
+ * for use.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getEvdoAsuLevel() {
return mCdma.getEvdoAsuLevel();
}
@@ -483,9 +629,15 @@
/**
* Get LTE as dBm
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getDbm}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteDbm() {
return mLte.getRsrp();
}
@@ -493,9 +645,15 @@
/**
* Get LTE as level 0..4
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteLevel() {
return mLte.getLevel();
}
@@ -504,26 +662,46 @@
* Get the LTE signal level as an asu value between 0..97, 99 is unknown
* Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getAsuLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteAsuLevel() {
return mLte.getAsuLevel();
}
/**
* @return true if this is for GSM
+ *
+ * @deprecated This method returns true if there are any 3gpp type SignalStrength elements in
+ * this SignalStrength report or if the report contains no valid SignalStrength
+ * information. Instead callers should use
+ * {@link android.telephony.SignalStrength#getCellSignalStrengths
+ * getCellSignalStrengths()} to determine which types of information are contained
+ * in the SignalStrength report.
*/
+ @Deprecated
public boolean isGsm() {
return !(getPrimary() instanceof CellSignalStrengthCdma);
}
/**
- * @return get TD_SCDMA dbm
+ * @return get TD-SCDMA dBm
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthTdscdma#getDbm}.
+ *
+ * @see android.telephony#CellSignalStrengthTdscdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getTdScdmaDbm() {
return mTdscdma.getRscp();
}
@@ -534,9 +712,15 @@
* INT_MAX: 0x7FFFFFFF denotes invalid value
* Reference: 3GPP TS 25.123, section 9.1.1.1
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthTdscdma#getLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthTdscdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getTdScdmaLevel() {
return mTdscdma.getLevel();
}
@@ -544,18 +728,30 @@
/**
* Get the TD-SCDMA signal level as an asu value.
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthTdscdma#getAsuLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthTdscdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getTdScdmaAsuLevel() {
return mTdscdma.getAsuLevel();
}
/**
- * Gets WCDMA RSCP as a dbm value between -120 and -24, as defined in TS 27.007 8.69.
+ * Gets WCDMA RSCP as a dBm value between -120 and -24, as defined in TS 27.007 8.69.
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthWcdma#getRscp}.
+ *
+ * @see android.telephony#CellSignalStrengthWcdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
+ @Deprecated
public int getWcdmaRscp() {
return mWcdma.getRscp();
}
@@ -563,8 +759,14 @@
/**
* Get the WCDMA signal level as an ASU value between 0-96, 255 is unknown
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthWcdma#getAsuLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthWcdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
+ @Deprecated
public int getWcdmaAsuLevel() {
/*
* 3GPP 27.007 (Ver 10.3.0) Sec 8.69
@@ -578,10 +780,16 @@
}
/**
- * Gets WCDMA signal strength as a dbm value between -120 and -24, as defined in TS 27.007 8.69.
+ * Gets WCDMA signal strength as a dBm value between -120 and -24, as defined in TS 27.007 8.69.
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthWcdma#getDbm}.
+ *
+ * @see android.telephony#CellSignalStrengthWcdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
+ @Deprecated
public int getWcdmaDbm() {
return mWcdma.getDbm();
}
@@ -589,13 +797,19 @@
/**
* Get WCDMA as level 0..4
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthWcdma#getDbm}.
+ *
+ * @see android.telephony#CellSignalStrengthWcdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
+ @Deprecated
public int getWcdmaLevel() {
return mWcdma.getLevel();
}
- /**
+ /**
* @return hash code
*/
@Override
@@ -639,9 +853,13 @@
* Set SignalStrength based on intent notifier map
*
* @param m intent notifier map
+ *
+ * @deprecated this method relies on non-stable implementation details, and full access to
+ * internal storage is available via {@link getCellSignalStrengths()}.
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private void setFromNotifierBundle(Bundle m) {
mCdma = m.getParcelable("Cdma");
mGsm = m.getParcelable("Gsm");
@@ -654,9 +872,13 @@
* Set intent notifier Bundle based on SignalStrength
*
* @param m intent notifier Bundle
+ *
+ * @deprecated this method relies on non-stable implementation details, and full access to
+ * internal storage is available via {@link getCellSignalStrengths()}.
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public void fillInNotifierBundle(Bundle m) {
m.putParcelable("Cdma", mCdma);
m.putParcelable("Gsm", mGsm);
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 1378bb0..d777bf1 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -22,6 +22,10 @@
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothMapClient;
+import android.bluetooth.BluetoothProfile;
import android.content.ActivityNotFoundException;
import android.content.ContentValues;
import android.content.Context;
@@ -32,6 +36,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.telecom.PhoneAccount;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -61,6 +66,8 @@
*/
public final class SmsManager {
private static final String TAG = "SmsManager";
+ private static final boolean DBG = false;
+
/**
* A psuedo-subId that represents the default subId at any given time. The actual subId it
* represents changes as the default subId is changed.
@@ -339,6 +346,44 @@
throw new IllegalArgumentException("Invalid message body");
}
+ // A Manager code accessing another manager is *not* acceptable, in Android.
+ // In this particular case, it is unavoidable because of the following:
+ // If the subscription for this SmsManager instance belongs to a remote SIM
+ // then a listener to get BluetoothMapClient proxy needs to be started up.
+ // Doing that is possible only in a foreground thread or as a system user.
+ // i.e., Can't be done in ISms service.
+ // For that reason, SubscriptionManager needs to be accessed here to determine
+ // if the subscription belongs to a remote SIM.
+ // Ideally, there should be another API in ISms to service messages going thru
+ // remote SIM subscriptions (and ISms should be tweaked to be able to access
+ // BluetoothMapClient proxy)
+ Context context = ActivityThread.currentApplication().getApplicationContext();
+ SubscriptionManager manager = (SubscriptionManager) context
+ .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ int subId = getSubscriptionId();
+ SubscriptionInfo info = manager.getActiveSubscriptionInfo(subId);
+ if (DBG) {
+ Log.d(TAG, "for subId: " + subId + ", subscription-info: " + info);
+ }
+ if (info == null) {
+ // There is no subscription for the given subId. That can only mean one thing:
+ // the caller is using a SmsManager instance with an obsolete subscription id.
+ // That is most probably because caller didn't invalidate SmsManager instance
+ // for an already deleted subscription id.
+ Log.e(TAG, "subId: " + subId + " for this SmsManager instance is obsolete.");
+ sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+ }
+
+ /* If the Subscription associated with this SmsManager instance belongs to a remote-sim,
+ * then send the message thru the remote-sim subscription.
+ */
+ if (info.getSubscriptionType() == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM) {
+ if (DBG) Log.d(TAG, "sending message thru bluetooth");
+ sendTextMessageBluetooth(destinationAddress, scAddress, text, sentIntent,
+ deliveryIntent, info);
+ return;
+ }
+
try {
ISms iccISms = getISmsServiceOrThrow();
iccISms.sendTextForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(),
@@ -350,6 +395,79 @@
}
}
+ private void sendTextMessageBluetooth(String destAddr, String scAddress,
+ String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
+ SubscriptionInfo info) {
+ BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (btAdapter == null) {
+ // No bluetooth service on this platform?
+ sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+ return;
+ }
+ BluetoothDevice device = btAdapter.getRemoteDevice(info.getIccId());
+ if (device == null) {
+ if (DBG) Log.d(TAG, "Bluetooth device addr invalid: " + info.getIccId());
+ sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+ return;
+ }
+ btAdapter.getProfileProxy(ActivityThread.currentApplication().getApplicationContext(),
+ new MapMessageSender(destAddr, text, device, sentIntent, deliveryIntent),
+ BluetoothProfile.MAP_CLIENT);
+ }
+
+ private class MapMessageSender implements BluetoothProfile.ServiceListener {
+ final Uri[] mDestAddr;
+ private String mMessage;
+ final BluetoothDevice mDevice;
+ final PendingIntent mSentIntent;
+ final PendingIntent mDeliveryIntent;
+ MapMessageSender(final String destAddr, final String message, final BluetoothDevice device,
+ final PendingIntent sentIntent, final PendingIntent deliveryIntent) {
+ super();
+ mDestAddr = new Uri[] {new Uri.Builder()
+ .appendPath(destAddr)
+ .scheme(PhoneAccount.SCHEME_TEL)
+ .build()};
+ mMessage = message;
+ mDevice = device;
+ mSentIntent = sentIntent;
+ mDeliveryIntent = deliveryIntent;
+ }
+
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (DBG) Log.d(TAG, "Service connected");
+ if (profile != BluetoothProfile.MAP_CLIENT) return;
+ BluetoothMapClient mapProfile = (BluetoothMapClient) proxy;
+ if (mMessage != null) {
+ if (DBG) Log.d(TAG, "Sending message thru bluetooth");
+ mapProfile.sendMessage(mDevice, mDestAddr, mMessage, mSentIntent, mDeliveryIntent);
+ mMessage = null;
+ }
+ BluetoothAdapter.getDefaultAdapter()
+ .closeProfileProxy(BluetoothProfile.MAP_CLIENT, mapProfile);
+ }
+
+ @Override
+ public void onServiceDisconnected(int profile) {
+ if (mMessage != null) {
+ if (DBG) Log.d(TAG, "Bluetooth disconnected before sending the message");
+ sendErrorInPendingIntent(mSentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+ mMessage = null;
+ }
+ }
+ }
+
+ private void sendErrorInPendingIntent(PendingIntent intent, int errorCode) {
+ try {
+ intent.send(errorCode);
+ } catch (PendingIntent.CanceledException e) {
+ // PendingIntent is cancelled. ignore sending this error code back to
+ // caller.
+ if (DBG) Log.d(TAG, "PendingIntent.CanceledException: " + e.getMessage());
+ }
+ }
+
/**
* Send a text based SMS without writing it into the SMS Provider.
*
@@ -888,8 +1006,6 @@
}
}
-
-
/**
* Get the SmsManager associated with the default subscription id. The instance will always be
* associated with the default subscription id, even if the default subscription id is changed.
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 51d5ab1..a3b3374 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -184,6 +184,11 @@
private int mProfileClass;
/**
+ * Type of subscription
+ */
+ private int mSubscriptionType;
+
+ /**
* @hide
*/
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
@@ -206,7 +211,8 @@
@Nullable String groupUUID, boolean isMetered, int carrierId, int profileClass) {
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1,
- isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass);
+ isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass,
+ SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
}
/**
@@ -217,7 +223,7 @@
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@Nullable UiccAccessRule[] accessRules, String cardString, int cardId,
boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered,
- boolean isGroupDisabled, int carrierid, int profileClass) {
+ boolean isGroupDisabled, int carrierId, int profileClass, int subType) {
this.mId = id;
this.mIccId = iccId;
this.mSimSlotIndex = simSlotIndex;
@@ -239,11 +245,11 @@
this.mGroupUUID = groupUUID;
this.mIsMetered = isMetered;
this.mIsGroupDisabled = isGroupDisabled;
- this.mCarrierId = carrierid;
+ this.mCarrierId = carrierId;
this.mProfileClass = profileClass;
+ this.mSubscriptionType = subType;
}
-
/**
* @return the subscription ID.
*/
@@ -487,6 +493,16 @@
}
/**
+ * This method returns the type of a subscription. It can be
+ * {@link SubscriptionManager#SUBSCRIPTION_TYPE_LOCAL_SIM} or
+ * {@link SubscriptionManager#SUBSCRIPTION_TYPE_REMOTE_SIM}.
+ * @return the type of subscription
+ */
+ public @SubscriptionManager.SubscriptionType int getSubscriptionType() {
+ return mSubscriptionType;
+ }
+
+ /**
* Checks whether the app with the given context is authorized to manage this subscription
* according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded}
* returns true).
@@ -612,11 +628,12 @@
boolean isGroupDisabled = source.readBoolean();
int carrierid = source.readInt();
int profileClass = source.readInt();
+ int subType = source.readInt();
return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
isEmbedded, accessRules, cardString, cardId, isOpportunistic, groupUUID,
- isMetered, isGroupDisabled, carrierid, profileClass);
+ isMetered, isGroupDisabled, carrierid, profileClass, subType);
}
@Override
@@ -650,6 +667,7 @@
dest.writeBoolean(mIsGroupDisabled);
dest.writeInt(mCarrierId);
dest.writeInt(mProfileClass);
+ dest.writeInt(mSubscriptionType);
}
@Override
@@ -686,7 +704,8 @@
+ " cardString=" + cardStringToPrint + " cardId=" + mCardId
+ " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID
+ " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled
- + " profileClass=" + mProfileClass + "}";
+ + " profileClass=" + mProfileClass
+ + " subscriptionType=" + mSubscriptionType + "}";
}
@Override
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 9fa4c3c..869cf1c 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -247,7 +247,9 @@
public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";
/**
- * TelephonyProvider column name for SIM ICC Identifier
+ * TelephonyProvider column name for a unique identifier for the subscription within the
+ * specific subscription type. For example, it contains SIM ICC Identifier subscriptions
+ * on Local SIMs. and Mac-address for Remote-SIM Subscriptions for Bluetooth devices.
* <P>Type: TEXT (String)</P>
*/
/** @hide */
@@ -265,6 +267,63 @@
public static final int SIM_NOT_INSERTED = -1;
/**
+ * The slot-index for Bluetooth Remote-SIM subscriptions
+ * @hide
+ */
+ public static final int SLOT_INDEX_FOR_REMOTE_SIM_SUB = INVALID_SIM_SLOT_INDEX;
+
+ /**
+ * TelephonyProvider column name Subscription-type.
+ * <P>Type: INTEGER (int)</P> {@link #SUBSCRIPTION_TYPE_LOCAL_SIM} for Local-SIM Subscriptions,
+ * {@link #SUBSCRIPTION_TYPE_REMOTE_SIM} for Remote-SIM Subscriptions.
+ * Default value is 0.
+ */
+ /** @hide */
+ public static final String SUBSCRIPTION_TYPE = "subscription_type";
+
+ /**
+ * This constant is to designate a subscription as a Local-SIM Subscription.
+ * <p> A Local-SIM can be a physical SIM inserted into a sim-slot in the device, or eSIM on the
+ * device.
+ * </p>
+ */
+ public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0;
+
+ /**
+ * This constant is to designate a subscription as a Remote-SIM Subscription.
+ * <p>
+ * A Remote-SIM subscription is for a SIM on a phone connected to this device via some
+ * connectivity mechanism, for example bluetooth. Similar to Local SIM, this subscription can
+ * be used for SMS, Voice and data by proxying data through the connected device.
+ * Certain data of the SIM, such as IMEI, are not accessible for Remote SIMs.
+ * </p>
+ *
+ * <p>
+ * A Remote-SIM is available only as long the phone stays connected to this device.
+ * When the phone disconnects, Remote-SIM subscription is removed from this device and is
+ * no longer known. All data associated with the subscription, such as stored SMS, call logs,
+ * contacts etc, are removed from this device.
+ * </p>
+ *
+ * <p>
+ * If the phone re-connects to this device, a new Remote-SIM subscription is created for
+ * the phone. The Subscription Id associated with the new subscription is different from
+ * the Subscription Id of the previous Remote-SIM subscription created (and removed) for the
+ * phone; i.e., new Remote-SIM subscription treats the reconnected phone as a Remote-SIM that
+ * was never seen before.
+ * </p>
+ */
+ public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SUBSCRIPTION_TYPE_"},
+ value = {
+ SUBSCRIPTION_TYPE_LOCAL_SIM,
+ SUBSCRIPTION_TYPE_REMOTE_SIM})
+ public @interface SubscriptionType {}
+
+ /**
* TelephonyProvider column name for user displayed name.
* <P>Type: TEXT (String)</P>
*/
@@ -1145,7 +1204,7 @@
}
/**
- * Get the SubscriptionInfo(s) of the currently inserted SIM(s). The records will be sorted
+ * Get the SubscriptionInfo(s) of the currently active SIM(s). The records will be sorted
* by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}.
*
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -1427,17 +1486,7 @@
logd("[addSubscriptionInfoRecord]- invalid slotIndex");
}
- try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
- if (iSub != null) {
- // FIXME: This returns 1 on success, 0 on error should should we return it?
- iSub.addSubInfoRecord(iccId, slotIndex);
- } else {
- logd("[addSubscriptionInfoRecord]- ISub service is null");
- }
- } catch (RemoteException ex) {
- // ignore it
- }
+ addSubscriptionInfoRecord(iccId, null, slotIndex, SUBSCRIPTION_TYPE_LOCAL_SIM);
// FIXME: Always returns null?
return null;
@@ -1445,6 +1494,79 @@
}
/**
+ * Add a new SubscriptionInfo to SubscriptionInfo database if needed
+ * @param uniqueId This is the unique identifier for the subscription within the
+ * specific subscription type.
+ * @param displayName human-readable name of the device the subscription corresponds to.
+ * @param slotIndex the slot assigned to this subscription. It is ignored for subscriptionType
+ * of {@link #SUBSCRIPTION_TYPE_REMOTE_SIM}.
+ * @param subscriptionType the {@link #SUBSCRIPTION_TYPE}
+ * @hide
+ */
+ public void addSubscriptionInfoRecord(String uniqueId, String displayName, int slotIndex,
+ int subscriptionType) {
+ if (VDBG) {
+ logd("[addSubscriptionInfoRecord]+ uniqueId:" + uniqueId
+ + ", displayName:" + displayName + ", slotIndex:" + slotIndex
+ + ", subscriptionType: " + subscriptionType);
+ }
+ if (uniqueId == null) {
+ Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null");
+ return;
+ }
+
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub == null) {
+ Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- ISub service is null");
+ return;
+ }
+ int result = iSub.addSubInfo(uniqueId, displayName, slotIndex, subscriptionType);
+ if (result < 0) {
+ Log.e(LOG_TAG, "Adding of subscription didn't succeed: error = " + result);
+ } else {
+ logd("successfully added new subscription");
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Remove SubscriptionInfo record from the SubscriptionInfo database
+ * @param uniqueId This is the unique identifier for the subscription within the specific
+ * subscription type.
+ * @param subscriptionType the {@link #SUBSCRIPTION_TYPE}
+ * @hide
+ */
+ public void removeSubscriptionInfoRecord(String uniqueId, int subscriptionType) {
+ if (VDBG) {
+ logd("[removeSubscriptionInfoRecord]+ uniqueId:" + uniqueId
+ + ", subscriptionType: " + subscriptionType);
+ }
+ if (uniqueId == null) {
+ Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null");
+ return;
+ }
+
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub == null) {
+ Log.e(LOG_TAG, "[removeSubscriptionInfoRecord]- ISub service is null");
+ return;
+ }
+ int result = iSub.removeSubInfo(uniqueId, subscriptionType);
+ if (result < 0) {
+ Log.e(LOG_TAG, "Removal of subscription didn't succeed: error = " + result);
+ } else {
+ logd("successfully removed subscription");
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
* Set SIM icon tint color by simInfo index
* @param tint the RGB value of icon tint color of the SIM
* @param subId the unique SubInfoRecord index in database
@@ -2737,6 +2859,95 @@
}
}
+ /**
+ * Enabled or disable a subscription. This is currently used in the settings page.
+ *
+ * <p>
+ * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+ *
+ * @param enable whether user is turning it on or off.
+ * @param subscriptionId Subscription to be enabled or disabled.
+ * It could be a eSIM or pSIM subscription.
+ *
+ * @return whether the operation is successful.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public boolean setSubscriptionEnabled(int subscriptionId, boolean enable) {
+ if (VDBG) {
+ logd("setSubscriptionActivated subId= " + subscriptionId + " enable " + enable);
+ }
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub != null) {
+ return iSub.setSubscriptionEnabled(enable, subscriptionId);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether the subscription is enabled or not. This is different from activated
+ * or deactivated for two aspects. 1) For when user disables a physical subscription, we
+ * actually disable the modem because we can't switch off the subscription. 2) For eSIM,
+ * user may enable one subscription but the system may activate another temporarily. In this
+ * case, user enabled one is different from current active one.
+
+ * @param subscriptionId The subscription it asks about.
+ * @return whether it's enabled or not. {@code true} if user set this subscription enabled
+ * earlier, or user never set subscription enable / disable on this slot explicitly, and
+ * this subscription is currently active. Otherwise, it returns {@code false}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean isSubscriptionEnabled(int subscriptionId) {
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub != null) {
+ return iSub.isSubscriptionEnabled(subscriptionId);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return false;
+ }
+
+ /**
+ * Get which subscription is enabled on this slot. See {@link #isSubscriptionEnabled(int)}
+ * for more details.
+ *
+ * @param slotIndex which slot it asks about.
+ * @return which subscription is enabled on this slot. If there's no enabled subscription
+ * in this slot, it will return {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public int getEnabledSubscriptionId(int slotIndex) {
+ int subId = INVALID_SUBSCRIPTION_ID;
+
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub != null) {
+ subId = iSub.getEnabledSubscriptionId(slotIndex);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ if (VDBG) logd("getEnabledSubscriptionId, subId = " + subId);
+ return subId;
+ }
+
private interface CallISubMethodHelper {
int callMethod(ISub iSub) throws RemoteException;
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 8053353..e710e0e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -93,6 +93,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.regex.Matcher;
@@ -8585,12 +8586,25 @@
}
- /** @hide */
- public String getLocaleFromDefaultSim() {
+ /**
+ * Returns a well-formed IETF BCP 47 language tag representing the locale from the SIM, e.g,
+ * en-US. Returns {@code null} if no locale could be derived from subscriptions.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+ *
+ * @see Locale#toLanguageTag()
+ * @see Locale#forLanguageTag(String)
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @Nullable public String getSimLocale() {
try {
final ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.getLocaleFromDefaultSim();
+ return telephony.getSimLocaleForSubscriber(getSubId());
}
} catch (RemoteException ex) {
}
@@ -8598,6 +8612,22 @@
}
/**
+ * TODO delete after SuW migrates to new API.
+ * @hide
+ */
+ public String getLocaleFromDefaultSim() {
+ try {
+ final ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getSimLocaleForSubscriber(getSubId());
+ }
+ } catch (RemoteException ex) {
+ }
+ return null;
+ }
+
+
+ /**
* Requests the modem activity info. The recipient will place the result
* in `result`.
* @param result The object on which the recipient will send the resulting
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 8d148c3..0e69530 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -140,15 +140,19 @@
@Retention(RetentionPolicy.SOURCE)
public @interface AuthType {}
- // Possible values for protocol.
- /** Protocol type for IP. */
+ // Possible values for protocol which is defined in TS 27.007 section 10.1.1.
+ /** Internet protocol. */
public static final int PROTOCOL_IP = 0;
- /** Protocol type for IPV6. */
+ /** Internet protocol, version 6. */
public static final int PROTOCOL_IPV6 = 1;
- /** Protocol type for IPV4V6. */
+ /** Virtual PDP type introduced to handle dual IP stack UE capability. */
public static final int PROTOCOL_IPV4V6 = 2;
- /** Protocol type for PPP. */
+ /** Point to point protocol. */
public static final int PROTOCOL_PPP = 3;
+ /** Transfer of Non-IP data to external packet data network. */
+ public static final int PROTOCOL_NON_IP = 4;
+ /** Transfer of Unstructured data to the Data Network via N6. */
+ public static final int PROTOCOL_UNSTRUCTURED = 5;
/** @hide */
@IntDef(prefix = { "PROTOCOL_" }, value = {
@@ -156,6 +160,8 @@
PROTOCOL_IPV6,
PROTOCOL_IPV4V6,
PROTOCOL_PPP,
+ PROTOCOL_NON_IP,
+ PROTOCOL_UNSTRUCTURED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProtocolType {}
@@ -217,11 +223,15 @@
PROTOCOL_STRING_MAP.put("IPV6", PROTOCOL_IPV6);
PROTOCOL_STRING_MAP.put("IPV4V6", PROTOCOL_IPV4V6);
PROTOCOL_STRING_MAP.put("PPP", PROTOCOL_PPP);
+ PROTOCOL_STRING_MAP.put("NON-IP", PROTOCOL_NON_IP);
+ PROTOCOL_STRING_MAP.put("UNSTRUCTURED", PROTOCOL_UNSTRUCTURED);
PROTOCOL_INT_MAP = new ArrayMap<Integer, String>();
PROTOCOL_INT_MAP.put(PROTOCOL_IP, "IP");
PROTOCOL_INT_MAP.put(PROTOCOL_IPV6, "IPV6");
PROTOCOL_INT_MAP.put(PROTOCOL_IPV4V6, "IPV4V6");
PROTOCOL_INT_MAP.put(PROTOCOL_PPP, "PPP");
+ PROTOCOL_INT_MAP.put(PROTOCOL_NON_IP, "NON-IP");
+ PROTOCOL_INT_MAP.put(PROTOCOL_UNSTRUCTURED, "UNSTRUCTURED");
MVNO_TYPE_STRING_MAP = new ArrayMap<String, Integer>();
MVNO_TYPE_STRING_MAP.put("spn", MVNO_TYPE_SPN);
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 25f5133..294c79b 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -52,8 +52,7 @@
* @param status Data call fail cause. 0 indicates no error.
* @param suggestedRetryTime The suggested data retry time in milliseconds.
* @param cid The unique id of the data connection.
- * @param active Data connection active status. 0 = inactive, 1 = active/physical link down,
- * 2 = active/physical link up.
+ * @param active Data connection active status. 0 = inactive, 1 = dormant, 2 = active.
* @param type The connection protocol, should be one of the PDP_type values in TS 27.007
* section 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP".
* @param ifname The network interface name.
@@ -124,7 +123,7 @@
public int getCallId() { return mCid; }
/**
- * @return 0 = inactive, 1 = active/physical link down, 2 = active/physical link up.
+ * @return 0 = inactive, 1 = dormant, 2 = active.
*/
public int getActive() { return mActive; }
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 0e5c71d..0fa1b41 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -129,6 +129,66 @@
"android.telephony.euicc.action.RESOLVE_ERROR";
/**
+ * Intent action sent by system apps (such as the Settings app) to the Telephony framework to
+ * enable or disable a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and
+ * {@link #EXTRA_ENABLE_SUBSCRIPTION}.
+ *
+ * <p>Unlike {@link #switchToSubscription(int, PendingIntent)}, using this action allows the
+ * underlying eUICC service (i.e. the LPA app) to control the UI experience during this
+ * operation. The action is received by the Telephony framework, which in turn selects and
+ * launches an appropriate LPA activity to present UI to the user. For example, the activity may
+ * show a confirmation dialog, a progress dialog, or an error dialog when necessary.
+ *
+ * <p>The launched activity will immediately finish with
+ * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED =
+ "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+
+ /**
+ * Intent action sent by system apps (such as the Settings app) to the Telephony framework to
+ * delete a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID}.
+ *
+ * <p>Unlike {@link #deleteSubscription(int, PendingIntent)}, using this action allows the
+ * underlying eUICC service (i.e. the LPA app) to control the UI experience during this
+ * operation. The action is received by the Telephony framework, which in turn selects and
+ * launches an appropriate LPA activity to present UI to the user. For example, the activity may
+ * show a confirmation dialog, a progress dialog, or an error dialog when necessary.
+ *
+ * <p>The launched activity will immediately finish with
+ * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED =
+ "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
+
+ /**
+ * Intent action sent by system apps (such as the Settings app) to the Telephony framework to
+ * rename a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and
+ * {@link #EXTRA_SUBSCRIPTION_NICKNAME}.
+ *
+ * <p>Unlike {@link #updateSubscriptionNickname(int, String, PendingIntent)}, using this action
+ * allows the the underlying eUICC service (i.e. the LPA app) to control the UI experience
+ * during this operation. The action is received by the Telephony framework, which in turn
+ * selects and launches an appropriate LPA activity to present UI to the user. For example, the
+ * activity may show a confirmation dialog, a progress dialog, or an error dialog when
+ * necessary.
+ *
+ * <p>The launched activity will immediately finish with
+ * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED =
+ "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
+
+ /**
* Result code for an operation indicating that the operation succeeded.
*/
public static final int EMBEDDED_SUBSCRIPTION_RESULT_OK = 0;
@@ -219,6 +279,37 @@
"android.telephony.euicc.extra.FORCE_PROVISION";
/**
+ * Key for an extra set on privileged actions {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED},
+ * {@link #ACTION_DELETE_SUBSCRIPTION_PRIVILEGED}, and
+ * {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing the ID of the targeted subscription.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_SUBSCRIPTION_ID =
+ "android.telephony.euicc.extra.SUBSCRIPTION_ID";
+
+ /**
+ * Key for an extra set on {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED} providing a boolean
+ * value of whether to enable or disable the targeted subscription.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_ENABLE_SUBSCRIPTION =
+ "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION";
+
+ /**
+ * Key for an extra set on {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing a new
+ * nickname for the targeted subscription.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_SUBSCRIPTION_NICKNAME =
+ "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME";
+
+ /**
* Optional meta-data attribute for a carrier app providing an icon to use to represent the
* carrier. If not provided, the app's launcher icon will be used as a fallback.
*/
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 577ddbd..a49d2d9 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -52,8 +52,8 @@
/**
* Get the active SubscriptionInfo associated with the slotIndex
* @param slotIndex the slot which the subscription is inserted
- * @param callingPackage The package maing the call.
- * @return SubscriptionInfo, maybe null if its not active
+ * @param callingPackage The package making the call.
+ * @return SubscriptionInfo, null for Remote-SIMs or non-active slotIndex.
*/
SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex, String callingPackage);
@@ -115,6 +115,26 @@
int addSubInfoRecord(String iccId, int slotIndex);
/**
+ * Add a new subscription info record, if needed
+ * @param uniqueId This is the unique identifier for the subscription within the specific
+ * subscription type.
+ * @param displayName human-readable name of the device the subscription corresponds to.
+ * @param slotIndex the slot assigned to this device
+ * @param subscriptionType the type of subscription to be added.
+ * @return 0 if success, < 0 on error.
+ */
+ int addSubInfo(String uniqueId, String displayName, int slotIndex, int subscriptionType);
+
+ /**
+ * Remove subscription info record for the given device.
+ * @param uniqueId This is the unique identifier for the subscription within the specific
+ * subscription type.
+ * @param subscriptionType the type of subscription to be removed
+ * @return 0 if success, < 0 on error.
+ */
+ int removeSubInfo(String uniqueId, int subscriptionType);
+
+ /**
* Set SIM icon tint color by simInfo index
* @param tint the icon tint color of the SIM
* @param subId the unique SubscriptionInfo index in database
@@ -256,6 +276,11 @@
String getSubscriptionProperty(int subId, String propKey, String callingPackage);
+ boolean setSubscriptionEnabled(boolean enable, int subId);
+
+ boolean isSubscriptionEnabled(int subId);
+
+ int getEnabledSubscriptionId(int slotIndex);
/**
* Get the SIM state for the slot index
* @return SIM state as the ordinal of IccCardConstants.State
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 5736a46..9cc173c 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1178,12 +1178,12 @@
void factoryReset(int subId);
/**
- * An estimate of the users's current locale based on the default SIM.
+ * Returns users's current locale based on the SIM.
*
* The returned string will be a well formed BCP-47 language tag, or {@code null}
* if no locale could be derived.
*/
- String getLocaleFromDefaultSim();
+ String getSimLocaleForSubscriber(int subId);
/**
* Requests the modem activity info asynchronously.
diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt
index a181bc38..1110790 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -32,7 +32,6 @@
public class MockContext extends android.content.Context {
ctor public MockContext();
- method public boolean bindIsolatedService(android.content.Intent, android.content.ServiceConnection, int, String);
method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
method public int checkCallingOrSelfPermission(String);
method public int checkCallingOrSelfUriPermission(android.net.Uri, int);
@@ -81,7 +80,6 @@
method public java.io.File getNoBackupFilesDir();
method public java.io.File getObbDir();
method public java.io.File[] getObbDirs();
- method public String getOpPackageName();
method public String getPackageCodePath();
method public android.content.pm.PackageManager getPackageManager();
method public String getPackageName();
@@ -137,7 +135,6 @@
method public boolean stopService(android.content.Intent);
method public void unbindService(android.content.ServiceConnection);
method public void unregisterReceiver(android.content.BroadcastReceiver);
- method public void updateServiceGroup(android.content.ServiceConnection, int, int);
}
@Deprecated public class MockCursor implements android.database.Cursor {
diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk
index ee2ec0a..979d13a 100644
--- a/tests/DexLoggerIntegrationTests/Android.mk
+++ b/tests/DexLoggerIntegrationTests/Android.mk
@@ -29,6 +29,35 @@
dexloggertest_jar := $(LOCAL_BUILT_MODULE)
+# Also build a native library that the test app can dynamically load
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := DexLoggerNativeTestLibrary
+LOCAL_MULTILIB := first
+LOCAL_SRC_FILES := src/cpp/com_android_dcl_Jni.cpp
+LOCAL_C_INCLUDES += \
+ $(JNI_H_INCLUDE)
+LOCAL_SDK_VERSION := 28
+LOCAL_NDK_STL_VARIANT := c++_static
+
+include $(BUILD_SHARED_LIBRARY)
+
+dexloggertest_so := $(LOCAL_BUILT_MODULE)
+
+# And a standalone native executable that we can exec.
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := DexLoggerNativeExecutable
+LOCAL_SRC_FILES := src/cpp/test_executable.cpp
+
+include $(BUILD_EXECUTABLE)
+
+dexloggertest_executable := $(LOCAL_BUILT_MODULE)
+
# Build the test app itself
include $(CLEAR_VARS)
@@ -37,14 +66,18 @@
LOCAL_PACKAGE_NAME := DexLoggerIntegrationTests
LOCAL_SDK_VERSION := current
LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
+LOCAL_CERTIFICATE := shared
LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm)
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
truth-prebuilt \
-# This gets us the javalib.jar built by DexLoggerTestLibrary above.
-LOCAL_JAVA_RESOURCE_FILES := $(dexloggertest_jar)
+# This gets us the javalib.jar built by DexLoggerTestLibrary above as well as the various
+# native binaries.
+LOCAL_JAVA_RESOURCE_FILES := \
+ $(dexloggertest_jar) \
+ $(dexloggertest_so) \
+ $(dexloggertest_executable)
include $(BUILD_PACKAGE)
diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
index 75ee089..d68769b 100644
--- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
+++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
@@ -17,6 +17,7 @@
package com.android.server.pm.dex;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.app.UiAutomation;
import android.content.Context;
@@ -25,6 +26,7 @@
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.util.EventLog;
+import android.util.EventLog.Event;
import dalvik.system.DexClassLoader;
@@ -65,14 +67,13 @@
// Event log tag used for SNET related events
private static final int SNET_TAG = 0x534e4554;
- // Subtag used to distinguish dynamic code loading events
- private static final String DCL_SUBTAG = "dcl";
+ // Subtags used to distinguish dynamic code loading events
+ private static final String DCL_DEX_SUBTAG = "dcl";
+ private static final String DCL_NATIVE_SUBTAG = "dcln";
- // All the tags we care about
- private static final int[] TAG_LIST = new int[] { SNET_TAG };
-
- // This is {@code DynamicCodeLoggingService#JOB_ID}
- private static final int DYNAMIC_CODE_LOGGING_JOB_ID = 2030028;
+ // These are job IDs from DynamicCodeLoggingService
+ private static final int IDLE_LOGGING_JOB_ID = 2030028;
+ private static final int AUDIT_WATCHING_JOB_ID = 203142925;
private static Context sContext;
private static int sMyUid;
@@ -89,15 +90,20 @@
// Without this the first test passes and others don't - we don't see new events in the
// log. The exact reason is unclear.
EventLog.writeEvent(SNET_TAG, "Dummy event");
+
+ // Audit log messages are throttled by the kernel (at the request of logd) to 5 per
+ // second, so running the tests too quickly in sequence means we lose some and get
+ // spurious failures. Sigh.
+ SystemClock.sleep(1000);
}
@Test
- public void testDexLoggerGeneratesEvents() throws Exception {
- File privateCopyFile = fileForJar("copied.jar");
+ public void testDexLoggerGeneratesEvents_standardClassLoader() throws Exception {
+ File privateCopyFile = privateFile("copied.jar");
// Obtained via "echo -n copied.jar | sha256sum"
String expectedNameHash =
"1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C";
- String expectedContentHash = copyAndHashJar(privateCopyFile);
+ String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile);
// Feed the jar to a class loader and make sure it contains what we expect.
ClassLoader parentClassLoader = sContext.getClass().getClassLoader();
@@ -107,18 +113,18 @@
// And make sure we log events about it
long previousEventNanos = mostRecentEventTimeNanos();
- runDexLogger();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
- assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash);
+ assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG,
+ expectedNameHash, expectedContentHash);
}
@Test
-
public void testDexLoggerGeneratesEvents_unknownClassLoader() throws Exception {
- File privateCopyFile = fileForJar("copied2.jar");
+ File privateCopyFile = privateFile("copied2.jar");
String expectedNameHash =
"202158B6A3169D78F1722487205A6B036B3F2F5653FDCFB4E74710611AC7EB93";
- String expectedContentHash = copyAndHashJar(privateCopyFile);
+ String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile);
// This time make sure an unknown class loader is an ancestor of the class loader we use.
ClassLoader knownClassLoader = sContext.getClass().getClassLoader();
@@ -129,22 +135,185 @@
// And make sure we log events about it
long previousEventNanos = mostRecentEventTimeNanos();
- runDexLogger();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
- assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash);
+ assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG,
+ expectedNameHash, expectedContentHash);
}
- private static File fileForJar(String name) {
- return new File(sContext.getDir("jars", Context.MODE_PRIVATE), name);
+ @Test
+ public void testDexLoggerGeneratesEvents_nativeLibrary() throws Exception {
+ File privateCopyFile = privateFile("copied.so");
+ String expectedNameHash =
+ "996223BAD4B4FE75C57A3DEC61DB9C0B38E0A7AD479FC95F33494F4BC55A0F0E";
+ String expectedContentHash =
+ copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+
+ System.load(privateCopyFile.toString());
+
+ // Run the job to scan generated audit log entries
+ runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+ // And then make sure we log events about it
+ long previousEventNanos = mostRecentEventTimeNanos();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+ assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+ expectedNameHash, expectedContentHash);
}
- private static String copyAndHashJar(File copyTo) throws Exception {
+ @Test
+ public void testDexLoggerGeneratesEvents_nativeLibrary_escapedName() throws Exception {
+ // A file name with a space will be escaped in the audit log; verify we un-escape it
+ // correctly.
+ File privateCopyFile = privateFile("second copy.so");
+ String expectedNameHash =
+ "8C39990C560B4F36F83E208E279F678746FE23A790E4C50F92686584EA2041CA";
+ String expectedContentHash =
+ copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+
+ System.load(privateCopyFile.toString());
+
+ // Run the job to scan generated audit log entries
+ runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+ // And then make sure we log events about it
+ long previousEventNanos = mostRecentEventTimeNanos();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+ assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+ expectedNameHash, expectedContentHash);
+ }
+
+ @Test
+ public void testDexLoggerGeneratesEvents_nativeExecutable() throws Exception {
+ File privateCopyFile = privateFile("test_executable");
+ String expectedNameHash =
+ "3FBEC3F925A132D18F347F11AE9A5BB8DE1238828F8B4E064AA86EB68BD46DCF";
+ String expectedContentHash =
+ copyAndHashResource("/DexLoggerNativeExecutable", privateCopyFile);
+ assertThat(privateCopyFile.setExecutable(true)).isTrue();
+
+ Process process = Runtime.getRuntime().exec(privateCopyFile.toString());
+ int exitCode = process.waitFor();
+ assertThat(exitCode).isEqualTo(0);
+
+ // Run the job to scan generated audit log entries
+ runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+ // And then make sure we log events about it
+ long previousEventNanos = mostRecentEventTimeNanos();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+ assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+ expectedNameHash, expectedContentHash);
+ }
+
+ @Test
+ public void testDexLoggerGeneratesEvents_spoofed_validFile() throws Exception {
+ File privateCopyFile = privateFile("spoofed");
+
+ String expectedContentHash =
+ copyAndHashResource("/DexLoggerNativeExecutable", privateCopyFile);
+
+ EventLog.writeEvent(EventLog.getTagCode("auditd"),
+ "type=1400 avc: granted { execute_no_trans } "
+ + "path=\"" + privateCopyFile + "\" "
+ + "scontext=u:r:untrusted_app_27: "
+ + "tcontext=u:object_r:app_data_file: "
+ + "tclass=file ");
+
+ String expectedNameHash =
+ "1CF36F503A02877BB775DC23C1C5A47A95F2684B6A1A83B11795B856D88861E3";
+
+ // Run the job to scan generated audit log entries
+ runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+ // And then make sure we log events about it
+ long previousEventNanos = mostRecentEventTimeNanos();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+ assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+ expectedNameHash, expectedContentHash);
+ }
+
+ @Test
+ public void testDexLoggerGeneratesEvents_spoofed_pathTraversal() throws Exception {
+ File privateDir = privateFile("x").getParentFile();
+
+ // Transform /a/b/c -> /a/b/c/../../.. so we get back to the root
+ File pathTraversalToRoot = privateDir;
+ File root = new File("/");
+ while (!privateDir.equals(root)) {
+ pathTraversalToRoot = new File(pathTraversalToRoot, "..");
+ privateDir = privateDir.getParentFile();
+ }
+
+ File spoofedFile = new File(pathTraversalToRoot, "dev/urandom");
+
+ assertWithMessage("Expected " + spoofedFile + " to be readable")
+ .that(spoofedFile.canRead()).isTrue();
+
+ EventLog.writeEvent(EventLog.getTagCode("auditd"),
+ "type=1400 avc: granted { execute_no_trans } "
+ + "path=\"" + spoofedFile + "\" "
+ + "scontext=u:r:untrusted_app_27: "
+ + "tcontext=u:object_r:app_data_file: "
+ + "tclass=file ");
+
+ String expectedNameHash =
+ "65528FE876BD676B0DFCC9A8ACA8988E026766F99EEC1E1FB48F46B2F635E225";
+
+ // Run the job to scan generated audit log entries
+ runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+ // And then trigger generating DCL events
+ long previousEventNanos = mostRecentEventTimeNanos();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+ assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash);
+ }
+
+ @Test
+ public void testDexLoggerGeneratesEvents_spoofed_otherAppFile() throws Exception {
+ File ourPath = sContext.getDatabasePath("android_pay");
+ File targetPath = new File(ourPath.toString()
+ .replace("com.android.frameworks.dexloggertest", "com.google.android.gms"));
+
+ assertWithMessage("Expected " + targetPath + " to not be readable")
+ .that(targetPath.canRead()).isFalse();
+
+ EventLog.writeEvent(EventLog.getTagCode("auditd"),
+ "type=1400 avc: granted { execute_no_trans } "
+ + "path=\"" + targetPath + "\" "
+ + "scontext=u:r:untrusted_app_27: "
+ + "tcontext=u:object_r:app_data_file: "
+ + "tclass=file ");
+
+ String expectedNameHash =
+ "CBE04E8AB9E7199FC19CBAAF9C774B88E56B3B19E823F2251693380AD6F515E6";
+
+ // Run the job to scan generated audit log entries
+ runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+ // And then trigger generating DCL events
+ long previousEventNanos = mostRecentEventTimeNanos();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+ assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash);
+ }
+
+ private static File privateFile(String name) {
+ return new File(sContext.getDir("dcl", Context.MODE_PRIVATE), name);
+ }
+
+ private static String copyAndHashResource(String resourcePath, File copyTo) throws Exception {
MessageDigest hasher = MessageDigest.getInstance("SHA-256");
// Copy the jar from our Java resources to a private data directory
Class<?> thisClass = DexLoggerIntegrationTests.class;
- try (InputStream input = thisClass.getResourceAsStream("/javalib.jar");
- OutputStream output = new FileOutputStream(copyTo)) {
+ try (InputStream input = thisClass.getResourceAsStream(resourcePath);
+ OutputStream output = new FileOutputStream(copyTo)) {
byte[] buffer = new byte[1024];
while (true) {
int numRead = input.read(buffer);
@@ -166,24 +335,18 @@
return formatter.toString();
}
- private static long mostRecentEventTimeNanos() throws Exception {
- List<EventLog.Event> events = new ArrayList<>();
-
- EventLog.readEvents(TAG_LIST, events);
- return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
- }
-
- private static void runDexLogger() throws Exception {
- // This forces {@code DynamicCodeLoggingService} to start now.
- runCommand("cmd jobscheduler run -f android " + DYNAMIC_CODE_LOGGING_JOB_ID);
+ private static void runDynamicCodeLoggingJob(int jobId) throws Exception {
+ // This forces the DynamicCodeLoggingService job to start now.
+ runCommand("cmd jobscheduler run -f android " + jobId);
// Wait for the job to have run.
long startTime = SystemClock.elapsedRealtime();
while (true) {
String response = runCommand(
- "cmd jobscheduler get-job-state android " + DYNAMIC_CODE_LOGGING_JOB_ID);
+ "cmd jobscheduler get-job-state android " + jobId);
if (!response.contains("pending") && !response.contains("active")) {
break;
}
+ // Don't wait forever - if it's taken > 10s then something is very wrong.
if (SystemClock.elapsedRealtime() - startTime > TimeUnit.SECONDS.toMillis(10)) {
throw new AssertionError("Job has not completed: " + response);
}
@@ -208,37 +371,68 @@
return response.toString("UTF-8");
}
- private static void assertDclLoggedSince(long previousEventNanos, String expectedNameHash,
- String expectedContentHash) throws Exception {
- List<EventLog.Event> events = new ArrayList<>();
- EventLog.readEvents(TAG_LIST, events);
- int found = 0;
- for (EventLog.Event event : events) {
+ private static long mostRecentEventTimeNanos() throws Exception {
+ List<Event> events = readSnetEvents();
+ return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
+ }
+
+ private static void assertDclLoggedSince(long previousEventNanos, String expectedSubTag,
+ String expectedNameHash, String expectedContentHash) throws Exception {
+ List<String> messages =
+ findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash);
+
+ assertWithMessage("Expected exactly one matching log entry").that(messages).hasSize(1);
+ assertThat(messages.get(0)).endsWith(expectedContentHash);
+ }
+
+ private static void assertNoDclLoggedSince(long previousEventNanos, String expectedSubTag,
+ String expectedNameHash) throws Exception {
+ List<String> messages =
+ findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash);
+
+ assertWithMessage("Expected no matching log entries").that(messages).isEmpty();
+ }
+
+ private static List<String> findMatchingEvents(long previousEventNanos, String expectedSubTag,
+ String expectedNameHash) throws Exception {
+ List<String> messages = new ArrayList<>();
+
+ for (Event event : readSnetEvents()) {
if (event.getTimeNanos() <= previousEventNanos) {
continue;
}
- Object[] data = (Object[]) event.getData();
- // We only care about DCL events that we generated.
- String subTag = (String) data[0];
- if (!DCL_SUBTAG.equals(subTag)) {
+ Object data = event.getData();
+ if (!(data instanceof Object[])) {
continue;
}
- int uid = (int) data[1];
+ Object[] fields = (Object[]) data;
+
+ // We only care about DCL events that we generated.
+ String subTag = (String) fields[0];
+ if (!expectedSubTag.equals(subTag)) {
+ continue;
+ }
+ int uid = (int) fields[1];
if (uid != sMyUid) {
continue;
}
- String message = (String) data[2];
+ String message = (String) fields[2];
if (!message.startsWith(expectedNameHash)) {
continue;
}
- assertThat(message).endsWith(expectedContentHash);
- ++found;
+ messages.add(message);
+ //assertThat(message).endsWith(expectedContentHash);
}
+ return messages;
+ }
- assertThat(found).isEqualTo(1);
+ private static List<Event> readSnetEvents() throws Exception {
+ List<Event> events = new ArrayList<>();
+ EventLog.readEvents(new int[] { SNET_TAG }, events);
+ return events;
}
/**
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp
similarity index 73%
rename from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
rename to tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp
index 62a8c48..0608883 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package com.android.testing.alarmservice;
+#include "jni.h"
-interface Alarm {
- int prepare();
- int setAlarmAndWait(long timeoutMills);
- int done();
-}
\ No newline at end of file
+extern "C" jint JNI_OnLoad(JavaVM* /* vm */, void* /* reserved */)
+{
+ return JNI_VERSION_1_6;
+}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp
similarity index 73%
copy from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
copy to tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp
index 62a8c48..ad025e6 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -14,10 +14,7 @@
* limitations under the License.
*/
-package com.android.testing.alarmservice;
-
-interface Alarm {
- int prepare();
- int setAlarmAndWait(long timeoutMills);
- int done();
-}
\ No newline at end of file
+int main() {
+ // This program just has to run, it doesn't need to do anything. So we don't.
+ return 0;
+}
diff --git a/tests/HwAccelerationTest/Android.mk b/tests/HwAccelerationTest/Android.mk
index 11ea954..79072fa 100644
--- a/tests/HwAccelerationTest/Android.mk
+++ b/tests/HwAccelerationTest/Android.mk
@@ -21,6 +21,7 @@
LOCAL_PACKAGE_NAME := HwAccelerationTest
LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_CERTIFICATE := platform
LOCAL_MODULE_TAGS := tests
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index c8f96c9..f330b83 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -310,6 +310,15 @@
<category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
+
+ <activity
+ android:name="PictureCaptureDemo"
+ android:label="Debug/Picture Capture">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.test.hwui.TEST" />
+ </intent-filter>
+ </activity>
<activity
android:name="SmallCircleActivity"
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PictureCaptureDemo.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PictureCaptureDemo.java
new file mode 100644
index 0000000..029e302
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PictureCaptureDemo.java
@@ -0,0 +1,153 @@
+/*
+ * 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.test.hwui;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Picture;
+import android.os.Bundle;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewDebug;
+import android.webkit.WebChromeClient;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.ProgressBar;
+
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Random;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+public class PictureCaptureDemo extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ final LinearLayout inner = new LinearLayout(this);
+ inner.setOrientation(LinearLayout.HORIZONTAL);
+ ProgressBar spinner = new ProgressBar(this, null, android.R.attr.progressBarStyleLarge);
+ inner.addView(spinner,
+ new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+
+ inner.addView(new View(this), new LayoutParams(50, 1));
+
+ Picture picture = new Picture();
+ Canvas canvas = picture.beginRecording(100, 100);
+ canvas.drawColor(Color.RED);
+ Paint paint = new Paint();
+ paint.setTextSize(32);
+ paint.setColor(Color.BLACK);
+ canvas.drawText("Hello", 0, 50, paint);
+ picture.endRecording();
+
+ ImageView iv1 = new ImageView(this);
+ iv1.setImageBitmap(Bitmap.createBitmap(picture, 100, 100, Bitmap.Config.ARGB_8888));
+ inner.addView(iv1, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+
+ inner.addView(new View(this), new LayoutParams(50, 1));
+
+ ImageView iv2 = new ImageView(this);
+ iv2.setImageBitmap(Bitmap.createBitmap(picture, 100, 100, Bitmap.Config.HARDWARE));
+ inner.addView(iv2, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+
+ layout.addView(inner,
+ new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+ // For testing with a functor in the tree
+ WebView wv = new WebView(this);
+ wv.setWebViewClient(new WebViewClient());
+ wv.setWebChromeClient(new WebChromeClient());
+ wv.loadUrl("https://google.com");
+ layout.addView(wv, new LayoutParams(LayoutParams.MATCH_PARENT, 400));
+
+ SurfaceView mySurfaceView = new SurfaceView(this);
+ layout.addView(mySurfaceView,
+ new LayoutParams(LayoutParams.MATCH_PARENT, 600));
+
+ setContentView(layout);
+
+ mySurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+ private AutoCloseable mStopCapture;
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ final Random rand = new Random();
+ mStopCapture = ViewDebug.startRenderingCommandsCapture(mySurfaceView,
+ mCaptureThread, (picture) -> {
+ if (rand.nextInt(20) == 0) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ }
+ }
+ Canvas canvas = holder.lockCanvas();
+ if (canvas == null) {
+ return false;
+ }
+ canvas.drawPicture(picture);
+ holder.unlockCanvasAndPost(canvas);
+ picture.close();
+ return true;
+ });
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ if (mStopCapture != null) {
+ try {
+ mStopCapture.close();
+ } catch (Exception e) {
+ }
+ mStopCapture = null;
+ }
+ }
+ });
+ }
+
+ ExecutorService mCaptureThread = Executors.newSingleThreadExecutor();
+ ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
+ Picture deepCopy(Picture src) {
+ try {
+ PipedInputStream inputStream = new PipedInputStream();
+ PipedOutputStream outputStream = new PipedOutputStream(inputStream);
+ Future<Picture> future = mExecutor.submit(() -> Picture.createFromStream(inputStream));
+ src.writeToStream(outputStream);
+ outputStream.close();
+ return future.get();
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+}
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
index d3c39f0..030641b 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
@@ -44,7 +44,6 @@
RollbackBroadcastReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
- filter.addDataScheme("package");
InstrumentationRegistry.getContext().registerReceiver(this, filter);
}
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index c2e735e..9d67cea 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -22,9 +22,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
-import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.test.InstrumentationRegistry;
@@ -38,7 +38,6 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -99,7 +98,7 @@
// so that's not the case!
for (int i = 0; i < 5; ++i) {
for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(info.targetPackage.packageName)) {
+ if (TEST_APP_A.equals(info.targetPackage.getPackageName())) {
Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect.");
Thread.sleep(1000);
break;
@@ -117,7 +116,7 @@
// There should be no recently executed rollbacks for this package.
for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
- assertNotEquals(TEST_APP_A, info.targetPackage.packageName);
+ assertNotEquals(TEST_APP_A, info.targetPackage.getPackageName());
}
// Install v1 of the app (without rollbacks enabled).
@@ -136,9 +135,7 @@
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollback);
- assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
- assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
// We should not have received any rollback requests yet.
// TODO: Possibly flaky if, by chance, some other app on device
@@ -154,21 +151,18 @@
// received could lead to test flakiness.
Intent broadcast = broadcastReceiver.poll(5, TimeUnit.SECONDS);
assertNotNull(broadcast);
- assertEquals(TEST_APP_A, broadcast.getData().getSchemeSpecificPart());
assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS));
// Verify the recent rollback has been recorded.
rollback = null;
for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(r.targetPackage.packageName)) {
+ if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
assertNull(rollback);
rollback = r;
}
}
assertNotNull(rollback);
- assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
- assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
broadcastReceiver.unregister();
context.unregisterReceiver(enableRollbackReceiver);
@@ -209,16 +203,12 @@
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
- assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
- assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
- assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
- assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
// Reload the persisted data.
rm.reloadPersistedData();
@@ -226,16 +216,12 @@
// The apps should still be available for rollback.
rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
- assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
- assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
- assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
- assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
// Rollback of B should not rollback A
RollbackTestUtils.rollback(rollbackB);
@@ -279,16 +265,12 @@
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
- assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
- assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
- assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
- assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
// Reload the persisted data.
rm.reloadPersistedData();
@@ -296,16 +278,12 @@
// The apps should still be available for rollback.
rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
- assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
- assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
- assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
- assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
// Rollback of B should rollback A as well
RollbackTestUtils.rollback(rollbackB);
@@ -349,15 +327,13 @@
// Verify the recent rollback has been recorded.
rollback = null;
for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(r.targetPackage.packageName)) {
+ if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
assertNull(rollback);
rollback = r;
}
}
assertNotNull(rollback);
- assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
- assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
// Reload the persisted data.
rm.reloadPersistedData();
@@ -365,15 +341,13 @@
// Verify the recent rollback is still recorded.
rollback = null;
for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(r.targetPackage.packageName)) {
+ if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
assertNull(rollback);
rollback = r;
}
}
assertNotNull(rollback);
- assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
- assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
} finally {
RollbackTestUtils.dropShellPermissionIdentity();
}
@@ -405,9 +379,7 @@
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollback);
- assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
- assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
// Expire the rollback.
rm.expireRollbackForPackage(TEST_APP_A);
@@ -470,7 +442,7 @@
* Test that app user data is rolled back.
* TODO: Stop ignoring this test once user data rollback is supported.
*/
- @Ignore @Test
+ @Test
public void testUserDataRollback() throws Exception {
try {
RollbackTestUtils.adoptShellPermissionIdentity(
@@ -479,9 +451,9 @@
Manifest.permission.MANAGE_ROLLBACKS);
RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppV1.apk", false);
+ RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
processUserData(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppV2.apk", true);
+ RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
processUserData(TEST_APP_A);
RollbackManager rm = RollbackTestUtils.getRollbackManager();
@@ -500,8 +472,7 @@
@Test
public void testRollbackBroadcastRestrictions() throws Exception {
RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
- Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED,
- Uri.fromParts("package", "com.android.tests.rollback.bogus", null));
+ Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
try {
InstrumentationRegistry.getContext().sendBroadcast(broadcast);
fail("Succeeded in sending restricted broadcast from app context.");
@@ -550,11 +521,11 @@
Thread.sleep(1000);
RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
- assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
+ assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName());
RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
- assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
+ assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName());
// Executing rollback should roll back the correct package.
RollbackTestUtils.rollback(rollbackA);
@@ -671,7 +642,7 @@
// We should not see a recent rollback listed for TEST_APP_B
for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- assertNotEquals(TEST_APP_B, r.targetPackage.packageName);
+ assertNotEquals(TEST_APP_B, r.targetPackage.getPackageName());
}
// TODO: Test the listed dependent apps for the recently executed
@@ -681,4 +652,15 @@
RollbackTestUtils.dropShellPermissionIdentity();
}
}
+
+ // Helper function to test the value of a PackageRollbackInfo
+ private void assertPackageRollbackInfoEquals(String packageName,
+ long versionRolledBackFrom, long versionRolledBackTo,
+ PackageRollbackInfo info) {
+ assertEquals(packageName, info.getPackageName());
+ assertEquals(packageName, info.getVersionRolledBackFrom().getPackageName());
+ assertEquals(versionRolledBackFrom, info.getVersionRolledBackFrom().getLongVersionCode());
+ assertEquals(packageName, info.getVersionRolledBackTo().getPackageName());
+ assertEquals(versionRolledBackTo, info.getVersionRolledBackTo().getLongVersionCode());
+ }
}
diff --git a/tests/UsageStatsTest/AndroidManifest.xml b/tests/UsageStatsTest/AndroidManifest.xml
index 4b1c1bd..fefd993 100644
--- a/tests/UsageStatsTest/AndroidManifest.xml
+++ b/tests/UsageStatsTest/AndroidManifest.xml
@@ -11,6 +11,7 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.OBSERVE_APP_USAGE" />
+ <uses-permission android:name="android.permission.SUSPEND_APPS" />
<application android:label="Usage Access Test">
<activity android:name=".UsageStatsActivity"
diff --git a/tests/UsageStatsTest/res/menu/main.xml b/tests/UsageStatsTest/res/menu/main.xml
index 612267c..272e0f4 100644
--- a/tests/UsageStatsTest/res/menu/main.xml
+++ b/tests/UsageStatsTest/res/menu/main.xml
@@ -6,4 +6,6 @@
android:title="Call isAppInactive()"/>
<item android:id="@+id/set_app_limit"
android:title="Set App Limit" />
+ <item android:id="@+id/set_app_usage_limit"
+ android:title="Set App Usage Limit" />
</menu>
diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
index 3c628f6..0105893 100644
--- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
+++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
@@ -21,6 +21,8 @@
import android.app.PendingIntent;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
+import android.content.ClipData;
+import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -49,6 +51,8 @@
private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
private static final String EXTRA_KEY_TIMEOUT = "com.android.tests.usagestats.extra.TIMEOUT";
private UsageStatsManager mUsageStatsManager;
+ private ClipboardManager mClipboard;
+ private ClipData mClip;
private Adapter mAdapter;
private Comparator<UsageStats> mComparator = new Comparator<UsageStats>() {
@Override
@@ -61,6 +65,7 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
+ mClipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
mAdapter = new Adapter();
setListAdapter(mAdapter);
Bundle extras = getIntent().getExtras();
@@ -98,6 +103,8 @@
case R.id.set_app_limit:
callSetAppLimit();
return true;
+ case R.id.set_app_usage_limit:
+ callSetAppUsageLimit();
default:
return super.onOptionsItemSelected(item);
}
@@ -170,6 +177,40 @@
builder.show();
}
+ private void callSetAppUsageLimit() {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Enter package name");
+ final EditText input = new EditText(this);
+ input.setInputType(InputType.TYPE_CLASS_TEXT);
+ input.setHint("com.android.tests.usagestats");
+ builder.setView(input);
+
+ builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final String packageName = input.getText().toString().trim();
+ if (!TextUtils.isEmpty(packageName)) {
+ String[] packages = packageName.split(",");
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClass(UsageStatsActivity.this, UsageStatsActivity.class);
+ intent.setPackage(getPackageName());
+ intent.putExtra(EXTRA_KEY_TIMEOUT, true);
+ mUsageStatsManager.registerAppUsageLimitObserver(1, packages,
+ 60, TimeUnit.SECONDS, PendingIntent.getActivity(UsageStatsActivity.this,
+ 1, intent, 0));
+ }
+ }
+ });
+ builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
+
+ builder.show();
+ }
+
private void showInactive(String packageName) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(
@@ -232,6 +273,21 @@
holder.packageName.setText(mStats.get(position).getPackageName());
holder.usageTime.setText(DateUtils.formatDuration(
mStats.get(position).getTotalTimeInForeground()));
+
+ //copy package name to the clipboard for convenience
+ holder.packageName.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ String text = holder.packageName.getText().toString();
+ mClip = ClipData.newPlainText("package_name", text);
+ mClipboard.setPrimaryClip(mClip);
+
+ Toast.makeText(getApplicationContext(), "package name copied to clipboard",
+ Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ });
+
return convertView;
}
}
diff --git a/tests/net/Android.mk b/tests/net/Android.mk
index 6850673..7e1b400 100644
--- a/tests/net/Android.mk
+++ b/tests/net/Android.mk
@@ -32,74 +32,6 @@
LOCAL_CERTIFICATE := platform
-# These are not normally accessible from apps so they must be explicitly included.
-LOCAL_JNI_SHARED_LIBRARIES := \
- android.hidl.token@1.0 \
- libartbase \
- libbacktrace \
- libbase \
- libbinder \
- libbinderthreadstate \
- libc++ \
- libcrypto \
- libcutils \
- libdexfile \
- libframeworksnettestsjni \
- libhidl-gen-utils \
- libhidlbase \
- libhidltransport \
- libhwbinder \
- liblog \
- liblzma \
- libnativehelper \
- libpackagelistparser \
- libpcre2 \
- libprocessgroup \
- libselinux \
- libui \
- libutils \
- libvintf \
- libvndksupport \
- libtinyxml2 \
- libunwindstack \
- libutilscallstack \
- libziparchive \
- libz \
- netd_aidl_interface-cpp
-
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
include $(BUILD_PACKAGE)
-
-#########################################################################
-# Build JNI Shared Library
-#########################################################################
-
-LOCAL_PATH:= $(LOCAL_PATH)/jni
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_CFLAGS := -Wall -Wextra -Werror
-
-LOCAL_C_INCLUDES := \
- libpcap \
- hardware/google/apf
-
-LOCAL_SRC_FILES := $(call all-cpp-files-under)
-
-LOCAL_SHARED_LIBRARIES := \
- libbinder \
- liblog \
- libcutils \
- libnativehelper \
- netd_aidl_interface-cpp
-
-LOCAL_STATIC_LIBRARIES := \
- libpcap \
- libapf
-
-LOCAL_MODULE := libframeworksnettestsjni
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/net/java/android/net/DnsPacketTest.java b/tests/net/java/android/net/DnsPacketTest.java
new file mode 100644
index 0000000..032e526
--- /dev/null
+++ b/tests/net/java/android/net/DnsPacketTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DnsPacketTest {
+ private void assertHeaderParses(DnsPacket.DnsHeader header, int id, int flag,
+ int qCount, int aCount, int nsCount, int arCount) {
+ assertEquals(header.id, id);
+ assertEquals(header.flags, flag);
+ assertEquals(header.getSectionCount(DnsPacket.QDSECTION), qCount);
+ assertEquals(header.getSectionCount(DnsPacket.ANSECTION), aCount);
+ assertEquals(header.getSectionCount(DnsPacket.NSSECTION), nsCount);
+ assertEquals(header.getSectionCount(DnsPacket.ARSECTION), arCount);
+ }
+
+ private void assertSectionParses(DnsPacket.DnsSection section, String dname,
+ int dtype, int dclass, int ttl, byte[] rr) {
+ assertEquals(section.dName, dname);
+ assertEquals(section.nsType, dtype);
+ assertEquals(section.nsClass, dclass);
+ assertEquals(section.ttl, ttl);
+ assertTrue(Arrays.equals(section.getRR(), rr));
+ }
+
+ class TestDnsPacket extends DnsPacket {
+ TestDnsPacket(byte[] data) throws ParseException {
+ super(data);
+ }
+
+ public DnsHeader getHeader() {
+ return mHeader;
+ }
+ public List<DnsSection> getSectionList(int secType) {
+ return mSections[secType];
+ }
+ }
+
+ @Test
+ public void testNullDisallowed() {
+ try {
+ new TestDnsPacket(null);
+ fail("Exception not thrown for null byte array");
+ } catch (DnsPacket.ParseException e) {
+ }
+ }
+
+ @Test
+ public void testV4Answer() throws Exception {
+ final byte[] v4blob = new byte[] {
+ /* Header */
+ 0x55, 0x66, /* Transaction ID */
+ (byte) 0x81, (byte) 0x80, /* Flags */
+ 0x00, 0x01, /* Questions */
+ 0x00, 0x01, /* Answer RRs */
+ 0x00, 0x00, /* Authority RRs */
+ 0x00, 0x00, /* Additional RRs */
+ /* Queries */
+ 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+ 0x00, 0x01, /* Type */
+ 0x00, 0x01, /* Class */
+ /* Answers */
+ (byte) 0xc0, 0x0c, /* Name */
+ 0x00, 0x01, /* Type */
+ 0x00, 0x01, /* Class */
+ 0x00, 0x00, 0x01, 0x2b, /* TTL */
+ 0x00, 0x04, /* Data length */
+ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */
+ };
+ TestDnsPacket packet = new TestDnsPacket(v4blob);
+
+ // Header part
+ assertHeaderParses(packet.getHeader(), 0x5566, 0x8180, 1, 1, 0, 0);
+
+ // Section part
+ List<DnsPacket.DnsSection> qdSectionList =
+ packet.getSectionList(DnsPacket.QDSECTION);
+ assertEquals(qdSectionList.size(), 1);
+ assertSectionParses(qdSectionList.get(0), "www.google.com", 1, 1, 0, null);
+
+ List<DnsPacket.DnsSection> anSectionList =
+ packet.getSectionList(DnsPacket.ANSECTION);
+ assertEquals(anSectionList.size(), 1);
+ assertSectionParses(anSectionList.get(0), "www.google.com", 1, 1, 0x12b,
+ new byte[]{ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 });
+ }
+
+ @Test
+ public void testV6Answer() throws Exception {
+ final byte[] v6blob = new byte[] {
+ /* Header */
+ 0x77, 0x22, /* Transaction ID */
+ (byte) 0x81, (byte) 0x80, /* Flags */
+ 0x00, 0x01, /* Questions */
+ 0x00, 0x01, /* Answer RRs */
+ 0x00, 0x00, /* Authority RRs */
+ 0x00, 0x00, /* Additional RRs */
+ /* Queries */
+ 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+ 0x00, 0x1c, /* Type */
+ 0x00, 0x01, /* Class */
+ /* Answers */
+ (byte) 0xc0, 0x0c, /* Name */
+ 0x00, 0x1c, /* Type */
+ 0x00, 0x01, /* Class */
+ 0x00, 0x00, 0x00, 0x37, /* TTL */
+ 0x00, 0x10, /* Data length */
+ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 /* Address */
+ };
+ TestDnsPacket packet = new TestDnsPacket(v6blob);
+
+ // Header part
+ assertHeaderParses(packet.getHeader(), 0x7722, 0x8180, 1, 1, 0, 0);
+
+ // Section part
+ List<DnsPacket.DnsSection> qdSectionList =
+ packet.getSectionList(DnsPacket.QDSECTION);
+ assertEquals(qdSectionList.size(), 1);
+ assertSectionParses(qdSectionList.get(0), "www.google.com", 28, 1, 0, null);
+
+ List<DnsPacket.DnsSection> anSectionList =
+ packet.getSectionList(DnsPacket.ANSECTION);
+ assertEquals(anSectionList.size(), 1);
+ assertSectionParses(anSectionList.get(0), "www.google.com", 28, 1, 0x37,
+ new byte[]{ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 });
+ }
+}
diff --git a/tests/net/java/android/net/ip/IpServerTest.java b/tests/net/java/android/net/ip/IpServerTest.java
index c3162af..80aac04 100644
--- a/tests/net/java/android/net/ip/IpServerTest.java
+++ b/tests/net/java/android/net/ip/IpServerTest.java
@@ -33,6 +33,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
@@ -47,6 +48,7 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.net.INetd;
import android.net.INetworkStatsService;
import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
@@ -92,6 +94,7 @@
private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000;
@Mock private INetworkManagementService mNMService;
+ @Mock private INetd mNetd;
@Mock private INetworkStatsService mStatsService;
@Mock private IpServer.Callback mCallback;
@Mock private InterfaceConfiguration mInterfaceConfiguration;
@@ -112,16 +115,6 @@
}
private void initStateMachine(int interfaceType, boolean usingLegacyDhcp) throws Exception {
- mIpServer = new IpServer(
- IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog,
- mNMService, mStatsService, mCallback, usingLegacyDhcp, mDependencies);
- mIpServer.start();
- // Starting the state machine always puts us in a consistent state and notifies
- // the rest of the world that we've changed from an unknown to available state.
- mLooper.dispatchAll();
- reset(mNMService, mStatsService, mCallback);
- when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
-
doAnswer(inv -> {
final IDhcpServerCallbacks cb = inv.getArgument(2);
new Thread(() -> {
@@ -135,6 +128,17 @@
}).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any());
when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS);
+ when(mDependencies.getNetdService()).thenReturn(mNetd);
+
+ mIpServer = new IpServer(
+ IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog,
+ mNMService, mStatsService, mCallback, usingLegacyDhcp, mDependencies);
+ mIpServer.start();
+ // Starting the state machine always puts us in a consistent state and notifies
+ // the rest of the world that we've changed from an unknown to available state.
+ mLooper.dispatchAll();
+ reset(mNMService, mStatsService, mCallback);
+ when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
when(mRaDaemon.start()).thenReturn(true);
}
@@ -223,9 +227,9 @@
initTetheredStateMachine(TETHERING_BLUETOOTH, null);
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
- InOrder inOrder = inOrder(mNMService, mStatsService, mCallback);
+ InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback);
inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
- inOrder.verify(mNMService).setInterfaceConfig(eq(IFACE_NAME), any());
+ inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
@@ -318,12 +322,12 @@
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
- InOrder inOrder = inOrder(mNMService, mStatsService, mCallback);
+ InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback);
inOrder.verify(mStatsService).forceUpdate();
inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
- inOrder.verify(mNMService).setInterfaceConfig(eq(IFACE_NAME), any());
+ inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
diff --git a/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java b/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java
index 6f711c0..b6d01db 100644
--- a/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java
+++ b/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java
@@ -48,61 +48,52 @@
private LinkProperties mLinkProperties;
private static final String TEST_LINKPROPS_IFACE = "TEST_IFACE";
- private static final String TEST_STACKED_LINK_1_IFACE = "TEST_STACKED_IFACE_1";
- private static final String TEST_STACKED_LINK_2_IFACE = "TEST_STACKED_IFACE_2";
@Before
public void setUp() {
- mLinkProperties = makeLinkProperties(TEST_LINKPROPS_IFACE);
- mLinkProperties.addStackedLink(makeLinkProperties(TEST_STACKED_LINK_1_IFACE));
- mLinkProperties.addStackedLink(makeLinkProperties(TEST_STACKED_LINK_2_IFACE));
- }
-
- private static LinkProperties makeLinkProperties(String iface) {
- final LinkProperties lp = new LinkProperties();
- lp.setInterfaceName(iface);
- lp.setLinkAddresses(Arrays.asList(
+ mLinkProperties = new LinkProperties();
+ mLinkProperties.setInterfaceName(TEST_LINKPROPS_IFACE);
+ mLinkProperties.setLinkAddresses(Arrays.asList(
new LinkAddress(InetAddresses.parseNumericAddress("192.168.0.42"), 16),
new LinkAddress(InetAddresses.parseNumericAddress("2001:db8::7"), 42)));
- lp.setDnsServers(Arrays.asList(
+ mLinkProperties.setDnsServers(Arrays.asList(
InetAddresses.parseNumericAddress("2001:db8::42"),
InetAddresses.parseNumericAddress("192.168.1.1")
));
- lp.setValidatedPrivateDnsServers(Arrays.asList(
+ mLinkProperties.setValidatedPrivateDnsServers(Arrays.asList(
InetAddresses.parseNumericAddress("2001:db8::43"),
InetAddresses.parseNumericAddress("192.168.42.43")
));
- lp.setPcscfServers(Arrays.asList(
+ mLinkProperties.setPcscfServers(Arrays.asList(
InetAddresses.parseNumericAddress("2001:db8::47"),
InetAddresses.parseNumericAddress("192.168.42.47")
));
- lp.setUsePrivateDns(true);
- lp.setPrivateDnsServerName("test.example.com");
- lp.setDomains("test1.example.com,test2.example.com");
- lp.addRoute(new RouteInfo(
+ mLinkProperties.setUsePrivateDns(true);
+ mLinkProperties.setPrivateDnsServerName("test.example.com");
+ mLinkProperties.setDomains("test1.example.com,test2.example.com");
+ mLinkProperties.addRoute(new RouteInfo(
new IpPrefix(InetAddresses.parseNumericAddress("2001:db8::44"), 45),
InetAddresses.parseNumericAddress("2001:db8::45"),
- iface,
+ TEST_LINKPROPS_IFACE,
RouteInfo.RTN_UNICAST
));
- lp.addRoute(new RouteInfo(
+ mLinkProperties.addRoute(new RouteInfo(
new IpPrefix(InetAddresses.parseNumericAddress("192.168.44.45"), 16),
InetAddresses.parseNumericAddress("192.168.45.1"),
- iface,
+ TEST_LINKPROPS_IFACE,
RouteInfo.RTN_THROW
));
- lp.setHttpProxy(new ProxyInfo("test3.example.com", 8000,
+ mLinkProperties.setHttpProxy(new ProxyInfo("test3.example.com", 8000,
"excl1.example.com,excl2.example.com"));
- lp.setMtu(5000);
- lp.setTcpBufferSizes("1,2,3,4,5,6");
- lp.setNat64Prefix(new IpPrefix(InetAddresses.parseNumericAddress("2001:db8::48"), 96));
+ mLinkProperties.setMtu(5000);
+ mLinkProperties.setTcpBufferSizes("1,2,3,4,5,6");
+ mLinkProperties.setNat64Prefix(
+ new IpPrefix(InetAddresses.parseNumericAddress("2001:db8::48"), 96));
// Verify that this test does not miss any new field added later.
// If any added field is not included in LinkProperties#equals, assertLinkPropertiesEquals
// must also be updated.
assertFieldCountEquals(14, LinkProperties.class);
-
- return lp;
}
@Test
@@ -186,7 +177,7 @@
private static void assertLinkPropertiesEquals(LinkProperties expected, LinkProperties actual) {
assertEquals(expected, actual);
- // LinkProperties equals() does not include stacked links
- assertEquals(expected.getStackedLinks(), actual.getStackedLinks());
+ // Equality on stacked links is not tested as they should not be passed to processes using
+ // LinkPropertiesParcelable.
}
}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 1c26418..923c7dd 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -107,6 +107,8 @@
import android.net.INetworkStatsService;
import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
+import android.net.IpSecManager;
+import android.net.IpSecManager.UdpEncapsulationSocket;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MatchAllNetworkSpecifier;
@@ -121,7 +123,9 @@
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
import android.net.NetworkUtils;
+import android.net.ProxyInfo;
import android.net.RouteInfo;
+import android.net.SocketKeepalive;
import android.net.UidRange;
import android.net.metrics.IpConnectivityLog;
import android.net.shared.NetworkMonitorUtils;
@@ -158,6 +162,7 @@
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.Nat464Xlat;
+import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
import com.android.server.net.NetworkPinner;
@@ -186,6 +191,8 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -402,8 +409,8 @@
private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
private int mScore;
private NetworkAgent mNetworkAgent;
- private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
- private int mStopKeepaliveError = PacketKeepalive.NO_KEEPALIVE;
+ private int mStartKeepaliveError = SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
+ private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE;
private Integer mExpectedKeepaliveSlot = null;
// Contains the redirectUrl from networkStatus(). Before reading, wait for
// mNetworkStatusReceived.
@@ -1002,6 +1009,11 @@
}
@Override
+ protected ProxyTracker makeProxyTracker() {
+ return mock(ProxyTracker.class);
+ }
+
+ @Override
protected int reserveNetId() {
while (true) {
final int netId = super.reserveNetId();
@@ -1023,6 +1035,11 @@
}
}
+ @Override
+ protected boolean queryUserAccess(int uid, int netId) {
+ return true;
+ }
+
public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) {
return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd;
}
@@ -1508,6 +1525,12 @@
verifyActiveNetwork(TRANSPORT_WIFI);
}
+ @Test
+ public void testRequiresValidation() {
+ assertTrue(NetworkMonitorUtils.isValidationRequired(
+ mCm.getDefaultRequest().networkCapabilities));
+ }
+
enum CallbackState {
NONE,
AVAILABLE,
@@ -3542,6 +3565,80 @@
}
}
+ private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback {
+
+ public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
+
+ private class CallbackValue {
+ public CallbackType callbackType;
+ public int error;
+
+ CallbackValue(CallbackType type) {
+ this.callbackType = type;
+ this.error = SocketKeepalive.SUCCESS;
+ assertTrue("onError callback must have error", type != CallbackType.ON_ERROR);
+ }
+
+ CallbackValue(CallbackType type, int error) {
+ this.callbackType = type;
+ this.error = error;
+ assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof CallbackValue
+ && this.callbackType == ((CallbackValue) o).callbackType
+ && this.error == ((CallbackValue) o).error;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType,
+ error);
+ }
+ }
+
+ private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
+
+ @Override
+ public void onStarted() {
+ mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED));
+ }
+
+ @Override
+ public void onStopped() {
+ mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED));
+ }
+
+ @Override
+ public void onError(int error) {
+ mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error));
+ }
+
+ private void expectCallback(CallbackValue callbackValue) {
+ try {
+ assertEquals(
+ callbackValue,
+ mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms");
+ }
+ }
+
+ public void expectStarted() {
+ expectCallback(new CallbackValue(CallbackType.ON_STARTED));
+ }
+
+ public void expectStopped() {
+ expectCallback(new CallbackValue(CallbackType.ON_STOPPED));
+ }
+
+ public void expectError(int error) {
+ expectCallback(new CallbackValue(CallbackType.ON_ERROR, error));
+ }
+ }
+
private Network connectKeepaliveNetwork(LinkProperties lp) {
// Ensure the network is disconnected before we do anything.
if (mWiFiNetworkAgent != null) {
@@ -3689,6 +3786,145 @@
}
@Test
+ public void testNattSocketKeepalives() throws Exception {
+ // TODO: 1. Move this outside of ConnectivityServiceTest.
+ // 2. Add helper function to test against newSingleThreadExecutor as well as inline
+ // executor.
+ // 3. Make test to verify that Nat-T keepalive socket is created by IpSecService.
+ final int srcPort = 12345;
+ final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
+ final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35");
+ final InetAddress myIPv6 = InetAddress.getByName("2001:db8::1");
+ final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8");
+ final InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888");
+
+ final int validKaInterval = 15;
+ final int invalidKaInterval = 9;
+
+ final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
+ final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort);
+
+ final Executor executor = Executors.newSingleThreadExecutor();
+
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("wlan12");
+ lp.addLinkAddress(new LinkAddress(myIPv6, 64));
+ lp.addLinkAddress(new LinkAddress(myIPv4, 25));
+ lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234")));
+ lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
+
+ Network notMyNet = new Network(61234);
+ Network myNet = connectKeepaliveNetwork(lp);
+
+ TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
+ SocketKeepalive ka;
+
+ // Attempt to start keepalives with invalid parameters and check for errors.
+ // Invalid network.
+ ka = mCm.createSocketKeepalive(notMyNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+
+ // Invalid interval.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(invalidKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_INTERVAL);
+
+ // Invalid destination.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv6, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+
+ // Invalid source;
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+
+ // NAT-T is only supported for IPv4.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv6, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+
+ // Sanity check before testing started keepalive.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+
+ // Check that a started keepalive can be stopped.
+ mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS);
+ ka.stop();
+ callback.expectStopped();
+
+ // Check that deleting the IP address stops the keepalive.
+ LinkProperties bogusLp = new LinkProperties(lp);
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25));
+ bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25));
+ mWiFiNetworkAgent.sendLinkProperties(bogusLp);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ mWiFiNetworkAgent.sendLinkProperties(lp);
+
+ // Check that a started keepalive is stopped correctly when the network disconnects.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ mWiFiNetworkAgent.disconnect();
+ waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+ callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+
+ // ... and that stopping it after that has no adverse effects.
+ waitForIdle();
+ final Network myNetAlias = myNet;
+ assertNull(mCm.getNetworkCapabilities(myNetAlias));
+ ka.stop();
+
+ // Reconnect.
+ myNet = connectKeepaliveNetwork(lp);
+ mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+
+ // Check things work as expected when the keepalive is stopped and the network disconnects.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ ka.stop();
+ mWiFiNetworkAgent.disconnect();
+ waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+ waitForIdle();
+ callback.expectStopped();
+
+ // Reconnect.
+ myNet = connectKeepaliveNetwork(lp);
+ mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+
+ // Check that keepalive slots start from 1 and increment. The first one gets slot 1.
+ mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectStarted();
+
+ // The second one gets slot 2.
+ mWiFiNetworkAgent.setExpectedKeepaliveSlot(2);
+ final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(6789);
+ TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback();
+ SocketKeepalive ka2 =
+ mCm.createSocketKeepalive(myNet, testSocket2, myIPv4, dstIPv4, executor, callback2);
+ ka2.start(validKaInterval);
+ callback2.expectStarted();
+
+ ka.stop();
+ callback.expectStopped();
+
+ ka2.stop();
+ callback2.expectStopped();
+ }
+
+ @Test
public void testGetCaptivePortalServerUrl() throws Exception {
String url = mCm.getCaptivePortalServerUrl();
assertEquals("http://connectivitycheck.gstatic.com/generate_204", url);
@@ -4404,8 +4640,7 @@
mMockVpn.setUids(ranges);
// VPN networks do not satisfy the default request and are automatically validated
// by NetworkMonitor
- assertFalse(NetworkMonitorUtils.isValidationRequired(
- mCm.getDefaultRequest().networkCapabilities, vpnNetworkAgent.mNetworkCapabilities));
+ assertFalse(NetworkMonitorUtils.isValidationRequired(vpnNetworkAgent.mNetworkCapabilities));
vpnNetworkAgent.setNetworkValid();
vpnNetworkAgent.connect(false);
@@ -4909,4 +5144,84 @@
mCellNetworkAgent.sendLinkProperties(lp);
verifyTcpBufferSizeChange(TEST_TCP_BUFFER_SIZES);
}
+
+ @Test
+ public void testGetGlobalProxyForNetwork() {
+ final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ final Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
+ when(mService.mProxyTracker.getGlobalProxy()).thenReturn(testProxyInfo);
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(wifiNetwork));
+ }
+
+ @Test
+ public void testGetProxyForActiveNetwork() {
+ final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(null));
+
+ final LinkProperties testLinkProperties = new LinkProperties();
+ testLinkProperties.setHttpProxy(testProxyInfo);
+
+ mWiFiNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
+ }
+
+ @Test
+ public void testGetProxyForVPN() {
+ final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+
+ // Set up a WiFi network with no proxy
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(null));
+
+ // Set up a VPN network with a proxy
+ final int uid = Process.myUid();
+ final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ ranges.add(new UidRange(uid, uid));
+ mMockVpn.setUids(ranges);
+ LinkProperties testLinkProperties = new LinkProperties();
+ testLinkProperties.setHttpProxy(testProxyInfo);
+ vpnNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+
+ // Connect to VPN with proxy
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ vpnNetworkAgent.connect(true);
+ mMockVpn.connect();
+ waitForIdle();
+
+ // Test that the VPN network returns a proxy, and the WiFi does not.
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(vpnNetworkAgent.getNetwork()));
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
+ assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
+
+ // Test that the VPN network returns no proxy when it is set to null.
+ testLinkProperties.setHttpProxy(null);
+ vpnNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(vpnNetworkAgent.getNetwork()));
+ assertNull(mService.getProxyForNetwork(null));
+
+ // Set WiFi proxy and check that the vpn proxy is still null.
+ testLinkProperties.setHttpProxy(testProxyInfo);
+ mWiFiNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(null));
+
+ // Disconnect from VPN and check that the active network, which is now the WiFi, has the
+ // correct proxy setting.
+ vpnNetworkAgent.disconnect();
+ waitForIdle();
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
+ }
}
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 125fe72..273b8fc 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
+
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
@@ -34,26 +35,24 @@
import android.content.res.Resources;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
-import android.support.test.runner.AndroidJUnit4;
import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import org.junit.runner.RunWith;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NetworkNotificationManagerTest {
@@ -194,4 +193,54 @@
mManager.clearNotification(id);
verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
}
+
+ @Test
+ public void testSameLevelNotifications() {
+ final int id = 101;
+ final String tag = NetworkNotificationManager.tagFor(id);
+
+ mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+ mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any());
+ }
+
+ @Test
+ public void testClearNotificationByType() {
+ final int id = 101;
+ final String tag = NetworkNotificationManager.tagFor(id);
+
+ // clearNotification(int id, NotificationType notifyType) will check if given type is equal
+ // to previous type or not. If they are equal then clear the notification; if they are not
+ // equal then return.
+
+ mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+ // Previous notification is LOGGED_IN and given type is LOGGED_IN too. The notification
+ // should be cleared.
+ mManager.clearNotification(id, LOGGED_IN);
+ verify(mNotificationManager, times(1))
+ .cancelAsUser(eq(tag), eq(LOGGED_IN.eventId), any());
+
+ mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(2))
+ .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+ // LOST_INTERNET notification popup after LOGGED_IN notification.
+ mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any());
+
+ // Previous notification is LOST_INTERNET and given type is LOGGED_IN. The notification
+ // shouldn't be cleared.
+ mManager.clearNotification(id, LOGGED_IN);
+ // LOST_INTERNET shouldn't be cleared.
+ verify(mNotificationManager, never())
+ .cancelAsUser(eq(tag), eq(LOST_INTERNET.eventId), any());
+ }
}
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index 1ea83c2..b635607 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -803,13 +803,14 @@
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
mLooper.dispatchAll();
- // We verify get/set called thrice here: once for setup and twice during
- // teardown because all events happen over the course of the single
+ // We verify get/set called thrice here: twice for setup (on NMService) and once during
+ // teardown (on Netd) because all events happen over the course of the single
// dispatchAll() above. Note that once the IpServer IPv4 address config
// code is refactored the two calls during shutdown will revert to one.
verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME);
- verify(mNMService, times(3))
+ verify(mNMService, times(2))
.setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
+ verify(mNetd, times(1)).interfaceSetCfg(argThat(p -> TEST_WLAN_IFNAME.equals(p.ifName)));
verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME);
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 9bf7587..0b74d87 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -57,7 +57,6 @@
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.ConnectivityManager;
-import android.net.IConnectivityManager;
import android.net.IpPrefix;
import android.net.LinkProperties;
import android.net.Network;
@@ -97,7 +96,6 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@@ -240,6 +238,30 @@
}
@Test
+ public void testGetAlwaysAndOnGetLockDown() throws Exception {
+ final Vpn vpn = createVpn(primaryUser.id);
+
+ // Default state.
+ assertFalse(vpn.getAlwaysOn());
+ assertFalse(vpn.getLockdown());
+
+ // Set always-on without lockdown.
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
+ assertTrue(vpn.getAlwaysOn());
+ assertFalse(vpn.getLockdown());
+
+ // Set always-on with lockdown.
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
+ assertTrue(vpn.getAlwaysOn());
+ assertTrue(vpn.getLockdown());
+
+ // Remove always-on configuration.
+ assertTrue(vpn.setAlwaysOnPackage(null, false));
+ assertFalse(vpn.getAlwaysOn());
+ assertFalse(vpn.getLockdown());
+ }
+
+ @Test
public void testLockdownChangingPackage() throws Exception {
final Vpn vpn = createVpn(primaryUser.id);
final UidRange user = UidRange.createForUser(primaryUser.id);
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
index 94bcd28..f2ecef9 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -27,10 +27,14 @@
import android.content.Context;
import android.net.ipmemorystore.Blob;
import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnL2KeyResponseListener;
import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
+import android.net.ipmemorystore.IOnSameNetworkResponseListener;
import android.net.ipmemorystore.IOnStatusListener;
import android.net.ipmemorystore.NetworkAttributes;
import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.SameL3NetworkResponse;
+import android.net.ipmemorystore.SameL3NetworkResponseParcelable;
import android.net.ipmemorystore.Status;
import android.net.ipmemorystore.StatusParcelable;
import android.os.IBinder;
@@ -53,7 +57,6 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
-import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -65,6 +68,15 @@
private static final String TEST_CLIENT_ID = "testClientId";
private static final String TEST_DATA_NAME = "testData";
+ private static final int FAKE_KEY_COUNT = 20;
+ private static final String[] FAKE_KEYS;
+ static {
+ FAKE_KEYS = new String[FAKE_KEY_COUNT];
+ for (int i = 0; i < FAKE_KEYS.length; ++i) {
+ FAKE_KEYS[i] = "fakeKey" + i;
+ }
+ }
+
@Mock
private Context mMockContext;
private File mDbFile;
@@ -130,8 +142,8 @@
final OnNetworkAttributesRetrievedListener functor) {
return new IOnNetworkAttributesRetrieved() {
@Override
- public void onL2KeyResponse(final StatusParcelable status, final String l2Key,
- final NetworkAttributesParcelable attributes)
+ public void onNetworkAttributesRetrieved(final StatusParcelable status,
+ final String l2Key, final NetworkAttributesParcelable attributes)
throws RemoteException {
functor.onNetworkAttributesRetrieved(new Status(status), l2Key,
null == attributes ? null : new NetworkAttributes(attributes));
@@ -144,6 +156,47 @@
};
}
+ /** Helper method to make an IOnSameNetworkResponseListener */
+ private interface OnSameNetworkResponseListener {
+ void onSameNetworkResponse(Status status, SameL3NetworkResponse answer);
+ }
+ private IOnSameNetworkResponseListener onSameResponse(
+ final OnSameNetworkResponseListener functor) {
+ return new IOnSameNetworkResponseListener() {
+ @Override
+ public void onSameNetworkResponse(final StatusParcelable status,
+ final SameL3NetworkResponseParcelable sameL3Network)
+ throws RemoteException {
+ functor.onSameNetworkResponse(new Status(status),
+ null == sameL3Network ? null : new SameL3NetworkResponse(sameL3Network));
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ };
+ }
+
+ /** Helper method to make an IOnL2KeyResponseListener */
+ private interface OnL2KeyResponseListener {
+ void onL2KeyResponse(Status status, String key);
+ }
+ private IOnL2KeyResponseListener onL2KeyResponse(final OnL2KeyResponseListener functor) {
+ return new IOnL2KeyResponseListener() {
+ @Override
+ public void onL2KeyResponse(final StatusParcelable status, final String key)
+ throws RemoteException {
+ functor.onL2KeyResponse(new Status(status), key);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ };
+ }
+
// Helper method to factorize some boilerplate
private void doLatched(final String timeoutMessage, final Consumer<CountDownLatch> functor) {
final CountDownLatch latch = new CountDownLatch(1);
@@ -155,24 +208,28 @@
}
}
+ // Helper methods to factorize more boilerplate
+ private void storeAttributes(final String l2Key, final NetworkAttributes na) {
+ storeAttributes("Did not complete storing attributes", l2Key, na);
+ }
+ private void storeAttributes(final String timeoutMessage, final String l2Key,
+ final NetworkAttributes na) {
+ doLatched(timeoutMessage, latch -> mService.storeNetworkAttributes(l2Key, na.toParcelable(),
+ onStatus(status -> {
+ assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+ latch.countDown();
+ })));
+ }
+
@Test
- public void testNetworkAttributes() {
+ public void testNetworkAttributes() throws UnknownHostException {
final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
- try {
- na.setAssignedV4Address(
- (Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4}));
- } catch (UnknownHostException e) { /* Can't happen */ }
+ na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
na.setGroupHint("hint1");
na.setMtu(219);
- final String l2Key = UUID.randomUUID().toString();
+ final String l2Key = FAKE_KEYS[0];
NetworkAttributes attributes = na.build();
- doLatched("Did not complete storing attributes", latch ->
- mService.storeNetworkAttributes(l2Key, attributes.toParcelable(),
- onStatus(status -> {
- assertTrue("Store status not successful : " + status.resultCode,
- status.isSuccess());
- latch.countDown();
- })));
+ storeAttributes(l2Key, attributes);
doLatched("Did not complete retrieving attributes", latch ->
mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
@@ -185,14 +242,10 @@
})));
final NetworkAttributes.Builder na2 = new NetworkAttributes.Builder();
- try {
- na.setDnsAddresses(Arrays.asList(
- new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
- } catch (UnknownHostException e) { /* Still can't happen */ }
+ na.setDnsAddresses(Arrays.asList(
+ new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
final NetworkAttributes attributes2 = na2.build();
- doLatched("Did not complete storing attributes 2", latch ->
- mService.storeNetworkAttributes(l2Key, attributes2.toParcelable(),
- onStatus(status -> latch.countDown())));
+ storeAttributes("Did not complete storing attributes 2", l2Key, attributes2);
doLatched("Did not complete retrieving attributes 2", latch ->
mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
@@ -268,7 +321,7 @@
public void testPrivateData() {
final Blob b = new Blob();
b.data = new byte[] { -3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34 };
- final String l2Key = UUID.randomUUID().toString();
+ final String l2Key = FAKE_KEYS[0];
doLatched("Did not complete storing private data", latch ->
mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
onStatus(status -> {
@@ -302,12 +355,139 @@
}
@Test
- public void testFindL2Key() {
- // TODO : implement this
+ public void testFindL2Key() throws UnknownHostException {
+ final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
+ na.setGroupHint("hint0");
+ storeAttributes(FAKE_KEYS[0], na.build());
+
+ na.setDnsAddresses(Arrays.asList(
+ new InetAddress[] {Inet6Address.getByName("8D56:9AF1::08EE:20F1")}));
+ na.setMtu(219);
+ storeAttributes(FAKE_KEYS[1], na.build());
+ na.setMtu(null);
+ na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+ na.setDnsAddresses(Arrays.asList(
+ new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
+ na.setGroupHint("hint1");
+ storeAttributes(FAKE_KEYS[2], na.build());
+ na.setMtu(219);
+ storeAttributes(FAKE_KEYS[3], na.build());
+ na.setMtu(240);
+ storeAttributes(FAKE_KEYS[4], na.build());
+ na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("5.6.7.8"));
+ storeAttributes(FAKE_KEYS[5], na.build());
+
+ // Matches key 5 exactly
+ doLatched("Did not finish finding L2Key", latch ->
+ mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(FAKE_KEYS[5], key);
+ })));
+
+ // MTU matches key 4 but v4 address matches key 5. The latter is stronger.
+ na.setMtu(240);
+ doLatched("Did not finish finding L2Key", latch ->
+ mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(FAKE_KEYS[5], key);
+ })));
+
+ // Closest to key 3 (indeed, identical)
+ na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+ na.setMtu(219);
+ doLatched("Did not finish finding L2Key", latch ->
+ mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(FAKE_KEYS[3], key);
+ })));
+
+ // Group hint alone must not be strong enough to override the rest
+ na.setGroupHint("hint0");
+ doLatched("Did not finish finding L2Key", latch ->
+ mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(FAKE_KEYS[3], key);
+ })));
+
+ // Still closest to key 3, though confidence is lower
+ na.setGroupHint("hint1");
+ na.setDnsAddresses(null);
+ doLatched("Did not finish finding L2Key", latch ->
+ mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(FAKE_KEYS[3], key);
+ })));
+
+ // But changing the MTU makes this closer to key 4
+ na.setMtu(240);
+ doLatched("Did not finish finding L2Key", latch ->
+ mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(FAKE_KEYS[4], key);
+ })));
+
+ // MTU alone not strong enough to make this group-close
+ na.setGroupHint(null);
+ na.setDnsAddresses(null);
+ na.setAssignedV4Address(null);
+ doLatched("Did not finish finding L2Key", latch ->
+ mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertNull(key);
+ })));
+ }
+
+ private void assertNetworksSameness(final String key1, final String key2, final int sameness) {
+ doLatched("Did not finish evaluating sameness", latch ->
+ mService.isSameNetwork(key1, key2, onSameResponse((status, answer) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(sameness, answer.getNetworkSameness());
+ })));
}
@Test
- public void testIsSameNetwork() {
- // TODO : implement this
+ public void testIsSameNetwork() throws UnknownHostException {
+ final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
+ na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+ na.setGroupHint("hint1");
+ na.setMtu(219);
+ na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")));
+
+ storeAttributes(FAKE_KEYS[0], na.build());
+ // 0 and 1 have identical attributes
+ storeAttributes(FAKE_KEYS[1], na.build());
+
+ // Hopefully only the MTU being different still means it's the same network
+ na.setMtu(200);
+ storeAttributes(FAKE_KEYS[2], na.build());
+
+ // Hopefully different MTU, assigned V4 address and grouphint make a different network,
+ // even with identical DNS addresses
+ na.setAssignedV4Address(null);
+ na.setGroupHint("hint2");
+ storeAttributes(FAKE_KEYS[3], na.build());
+
+ assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[1], SameL3NetworkResponse.NETWORK_SAME);
+ assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[2], SameL3NetworkResponse.NETWORK_SAME);
+ assertNetworksSameness(FAKE_KEYS[1], FAKE_KEYS[2], SameL3NetworkResponse.NETWORK_SAME);
+ assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[3], SameL3NetworkResponse.NETWORK_DIFFERENT);
+ assertNetworksSameness(FAKE_KEYS[0], "neverInsertedKey",
+ SameL3NetworkResponse.NETWORK_NEVER_CONNECTED);
+
+ doLatched("Did not finish evaluating sameness", latch ->
+ mService.isSameNetwork(null, null, onSameResponse((status, answer) -> {
+ assertFalse("Retrieve network sameness suspiciously successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+ assertNull(answer);
+ })));
}
}
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
new file mode 100644
index 0000000..fe19eee
--- /dev/null
+++ b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.net.ipmemorystore;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.ipmemorystore.NetworkAttributes;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.net.Inet4Address;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+/** Unit tests for {@link NetworkAttributes}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NetworkAttributesTest {
+ private static final String WEIGHT_FIELD_NAME_PREFIX = "WEIGHT_";
+ private static final float EPSILON = 0.0001f;
+
+ // This is running two tests to make sure the total weight is the sum of all weights. To be
+ // sure this is not fireproof, but you'd kind of need to do it on purpose to pass.
+ @Test
+ public void testTotalWeight() throws IllegalAccessException, UnknownHostException {
+ // Make sure that TOTAL_WEIGHT is equal to the sum of the fields starting with WEIGHT_
+ float sum = 0f;
+ final Field[] fieldList = NetworkAttributes.class.getDeclaredFields();
+ for (final Field field : fieldList) {
+ if (!field.getName().startsWith(WEIGHT_FIELD_NAME_PREFIX)) continue;
+ field.setAccessible(true);
+ sum += (float) field.get(null);
+ }
+ assertEquals(sum, NetworkAttributes.TOTAL_WEIGHT, EPSILON);
+
+ // Use directly the constructor with all attributes, and make sure that when compared
+ // to itself the score is a clean 1.0f.
+ final NetworkAttributes na =
+ new NetworkAttributes(
+ (Inet4Address) Inet4Address.getByAddress(new byte[] {1, 2, 3, 4}),
+ "some hint",
+ Arrays.asList(Inet4Address.getByAddress(new byte[] {5, 6, 7, 8}),
+ Inet4Address.getByAddress(new byte[] {9, 0, 1, 2})),
+ 98);
+ assertEquals(1.0f, na.getNetworkGroupSamenessConfidence(na), EPSILON);
+ }
+}
diff --git a/tests/utils/SleepUtils/AlarmService/Android.mk b/tests/utils/SleepUtils/AlarmService/Android.mk
deleted file mode 100644
index 9022f03..0000000
--- a/tests/utils/SleepUtils/AlarmService/Android.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Copyright (C) 2013 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.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SRC_FILES += \
- src/com/android/testing/alarmservice/Alarm.aidl
-LOCAL_PACKAGE_NAME := SleepUtilsAlarmService
-LOCAL_SDK_VERSION := 7
-include $(BUILD_PACKAGE)
diff --git a/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml b/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml
deleted file mode 100644
index 1b6de39..0000000
--- a/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.testing.alarmservice" >
-
- <uses-sdk android:minSdkVersion="7" />
- <uses-permission android:name="android.permission.WAKE_LOCK" />
-
- <application android:label="Sleep Utils Alarm Service">
- <service android:name=".AlarmService"
- android:label="Sleep Utils Alarm Service"
- android:exported="true"
- android:enabled="true">
- <intent-filter>
- <action android:name="com.android.testing.ALARM_SERVICE" />
- </intent-filter>
- </service>
- <receiver android:name=".WakeUpCall">
- <intent-filter>
- <action android:name="com.android.testing.alarmservice.WAKEUP" />
- </intent-filter>
- </receiver>
- </application>
-</manifest>
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java
deleted file mode 100644
index 122d55d..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.alarmservice;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.testing.alarmservice.Alarm.Stub;
-
-public class AlarmImpl extends Stub {
-
- private static final String LOG_TAG = AlarmImpl.class.getSimpleName();
-
- private Context mContext;
-
- public AlarmImpl(Context context) {
- super();
- mContext = context;
- }
-
- @Override
- public int prepare() throws RemoteException {
- WakeUpController.getController().getWakeLock().acquire();
- Log.d(LOG_TAG, "AlarmService prepared, wake lock acquired");
- return 0;
- }
-
- @Override
- public int setAlarmAndWait(long timeoutMills) throws RemoteException {
- // calculate when device should be waken up
- long atTime = SystemClock.elapsedRealtime() + timeoutMills;
- AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- Intent wakupIntent = new Intent(WakeUpCall.WAKEUP_CALL);
- PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, wakupIntent, 0);
- // set alarm, which will be delivered in form of the wakeupIntent
- am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
- Log.d(LOG_TAG, String.format("Alarm set: %d, giving up wake lock", atTime));
- Object lock = WakeUpController.getController().getWakeSync();
- // release wakelock and wait for the lock to be poked from the broadcast receiver
- WakeUpController.getController().getWakeLock().release();
- // does not really matter if device enters suspend before we start waiting on lock
- synchronized (lock) {
- try {
- lock.wait();
- } catch (InterruptedException e) {
- }
- }
- Log.d(LOG_TAG, String.format("Alarm triggered, done waiting"));
- return 0;
- }
-
- @Override
- public int done() throws RemoteException {
- WakeUpController.getController().getWakeLock().release();
- return 0;
- }
-
-}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java
deleted file mode 100644
index 576a1cf..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.alarmservice;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.IBinder;
-
-public class AlarmService extends Service {
-
- private AlarmImpl mAlarmImpl = null;
- static Context sContext;
-
- @Override
- public void onCreate() {
- super.onCreate();
- sContext = this;
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return getAlarmImpl();
- }
-
- private AlarmImpl getAlarmImpl() {
- if (mAlarmImpl == null) {
- mAlarmImpl = new AlarmImpl(this);
- }
- return mAlarmImpl;
- }
-
- @Override
- public void onDestroy() {
- sContext = null;
- super.onDestroy();
- }
-}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java
deleted file mode 100644
index f4bb4db..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.alarmservice;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * The receiver for the alarm we set
- *
- */
-public class WakeUpCall extends BroadcastReceiver {
-
- public static final String WAKEUP_CALL = "com.android.testing.alarmservice.WAKEUP";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- // we acquire wakelock without release because user is supposed to manually release it
- WakeUpController.getController().getWakeLock().acquire();
- Object lock = WakeUpController.getController().getWakeSync();
- synchronized (lock) {
- // poke the lock so the service side can be woken from waiting on the lock
- lock.notifyAll();
- }
- }
-
-}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java
deleted file mode 100644
index 478371f..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.alarmservice;
-
-import android.content.Context;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.util.Log;
-
-/**
- * A singleton used for controlling and sharing of states/wakelocks
- *
- */
-public class WakeUpController {
-
- private static final String LOG_TAG = WakeUpController.class.getName();
- private static WakeUpController mController = null;
- private WakeLock mWakeLock = null;
- private Object mWakeSync = new Object();
-
- private WakeUpController() {
- Log.i(LOG_TAG, "Created instance: 0x" + Integer.toHexString(this.hashCode()));
- }
-
- public static synchronized WakeUpController getController() {
- if (mController == null) {
- mController = new WakeUpController();
- }
- return mController;
- }
-
- public WakeLock getWakeLock() {
- if (mWakeLock == null) {
- PowerManager pm =
- (PowerManager) AlarmService.sContext.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "testing-alarmservice");
- Log.i(LOG_TAG, "Create wakelock: 0x" + Integer.toHexString(mWakeLock.hashCode()));
- }
- return mWakeLock;
- }
-
- public Object getWakeSync() {
- return mWakeSync;
- }
-}
diff --git a/tests/utils/SleepUtils/Android.mk b/tests/utils/SleepUtils/Android.mk
deleted file mode 100644
index 0e65e22..0000000
--- a/tests/utils/SleepUtils/Android.mk
+++ /dev/null
@@ -1,2 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/tests/utils/SleepUtils/README b/tests/utils/SleepUtils/README
deleted file mode 100644
index bfe07da..0000000
--- a/tests/utils/SleepUtils/README
+++ /dev/null
@@ -1,23 +0,0 @@
-This folder contains utils to properly perform timed suspend and wakeup.
-
-AlarmService - a service that client can bind to and perform:
-1) holding wakelock (singleton to this service)
-2) setting alarm for a specified period and releasing the wakelock; service
- call will block until alarm has been triggered and the wakelock is held
-3) releasing the wakelock
-
-SleepHelper - a self instrumentation meant as a convenient way to trigger
-the service functions from command line. Corresponding to service function
-above, supported operations are:
-1) holding wakelock
-am instrument -w -e command prepare \
- com.android.testing.sleephelper/.SetAlarm
-
-2) setting alarm and wait til triggered
-am instrument -w -e command set_wait \
- -e param <time in ms> com.android.testing.sleephelper/.SetAlarm
-Note: for the function to work properly, "-w" parameter is required
-
-3) releasing wakelock
-am instrument -w -e command done \
- com.android.testing.sleephelper/.SetAlarm
diff --git a/tests/utils/SleepUtils/SleepHelper/Android.mk b/tests/utils/SleepUtils/SleepHelper/Android.mk
deleted file mode 100644
index f8267fd..0000000
--- a/tests/utils/SleepUtils/SleepHelper/Android.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Copyright (C) 2013 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.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-# Only compile source java files in this apk.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SRC_FILES += \
- ../AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
-LOCAL_SDK_VERSION := 7
-LOCAL_PACKAGE_NAME := SleepUtilsSleepHelper
-
-include $(BUILD_PACKAGE)
diff --git a/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml b/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml
deleted file mode 100644
index 0f1d491..0000000
--- a/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.testing.sleephelper">
-
- <uses-sdk android:minSdkVersion="7" />
- <instrumentation android:label="Sleep Helper"
- android:name="com.android.testing.sleephelper.SetAlarm"
- android:targetPackage="com.android.testing.sleephelper" />
-
- <application android:label="Sleep Utils Sleep Helper">
- <uses-library android:name="android.test.runner" />
- </application>
-</manifest>
diff --git a/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java b/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java
deleted file mode 100644
index b558741..0000000
--- a/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.sleephelper;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.Debug;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.testing.alarmservice.Alarm;
-
-public class SetAlarm extends Instrumentation {
-
- private static final String COMMAND = "command";
- private static final String PARAM = "param";
- private static final String CMD_PREPARE = "prepare";
- private static final String CMD_SET = "set_wait";
- private static final String CMD_DONE = "done";
- private static final String SERVICE_ACTION = "com.android.testing.ALARM_SERVICE";
- private static final String SERVICE_PKG = "com.android.testing.alarmservice";
- private static final String LOG_TAG = SetAlarm.class.getSimpleName();
-
- private Alarm mAlarmService = null;
- private Bundle mArgs = null;
- private String mCommand = null;
- private Intent mServceIntent = new Intent(SERVICE_ACTION).setPackage(SERVICE_PKG);
-
- private ServiceConnection mConn = new ServiceConnection() {
- @Override
- public void onServiceDisconnected(ComponentName name) {
- Log.d(LOG_TAG, "Service disconnected.");
- mAlarmService = null;
- errorFinish("service disconnected");
- }
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- Log.d(LOG_TAG, "Service connected.");
- mAlarmService = Alarm.Stub.asInterface(service);
- handleCommands();
- }
- };
-
-
- private void handleCommands() {
- if (CMD_PREPARE.equals(mCommand)) {
- callPrepare();
- } else if (CMD_SET.equals(mCommand)) {
- String paramString = mArgs.getString(PARAM);
- if (paramString == null) {
- errorFinish("argument expected for alarm time");
- }
- long timeout = -1;
- try {
- timeout = Long.parseLong(paramString);
- } catch (NumberFormatException nfe) {
- errorFinish("a number argument is expected");
- }
- callSetAndWait(timeout);
- } else if (CMD_DONE.equals(mCommand)) {
- callDone();
- } else {
- errorFinish("Unrecognized command: " + mCommand);
- }
- finish(Activity.RESULT_OK, new Bundle());
- }
-
- @Override
- public void onCreate(Bundle arguments) {
- super.onCreate(arguments);
- mCommand = arguments.getString(COMMAND);
- if ("true".equals(arguments.getString("debug"))) {
- Debug.waitForDebugger();
- }
- if (mCommand == null) {
- errorFinish("No command specified");
- }
- mArgs = arguments;
- connectToAlarmService();
- }
-
- private void errorFinish(String msg) {
- Bundle ret = new Bundle();
- ret.putString("error", msg);
- finish(Activity.RESULT_CANCELED, ret);
- }
-
- private void connectToAlarmService() {
- // start the service with an intent, this ensures the service keeps running after unbind
- ComponentName cn = getContext().startService(mServceIntent);
- if (cn == null) {
- errorFinish("failed to start service");
- }
- if (!getContext().bindService(mServceIntent, mConn, Context.BIND_AUTO_CREATE)) {
- errorFinish("failed to bind service");
- }
- }
-
- private void callPrepare() {
- try {
- mAlarmService.prepare();
- } catch (RemoteException e) {
- errorFinish("RemoteExeption in prepare()");
- } finally {
- getContext().unbindService(mConn);
- }
- }
-
- private void callDone() {
- try {
- mAlarmService.done();
- } catch (RemoteException e) {
- errorFinish("RemoteExeption in prepare()");
- } finally {
- getContext().unbindService(mConn);
- }
- // explicitly stop the service (started in prepare()) so that the service is now free
- // to be reclaimed
- getContext().stopService(mServceIntent);
- }
-
- private void callSetAndWait(long timeoutMills) {
- try {
- mAlarmService.setAlarmAndWait(timeoutMills);
- } catch (RemoteException e) {
- errorFinish("RemoteExeption in setAlarmAndWait()");
- } finally {
- getContext().unbindService(mConn);
- }
- }
-}
diff --git a/tests/utils/SleepUtils/WakeLoopService/Android.mk b/tests/utils/SleepUtils/WakeLoopService/Android.mk
deleted file mode 100644
index a8a944b..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/Android.mk
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Copyright (C) 2014 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.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := WakeupLoopService
-LOCAL_SDK_VERSION := 7
-include $(BUILD_PACKAGE)
diff --git a/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml b/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml
deleted file mode 100644
index a7028c4..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 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="android.test.wakeuploop" >
-
- <uses-sdk android:minSdkVersion="7" />
- <uses-permission android:name="android.permission.WAKE_LOCK" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
- <application android:label="Auto Wakeup Loop">
- <service android:name=".WakeLoopService"
- android:label="Wakup Loop Service"
- android:exported="true"
- android:enabled="true">
- <intent-filter>
- <action android:name="android.test.wakeuploop.WAKEUP_SERVICE" />
- </intent-filter>
- </service>
- <receiver android:name=".WakeUpCall">
- <intent-filter>
- <action android:name="android.test.wakeuploop.WAKEUP" />
- </intent-filter>
- </receiver>
- </application>
-</manifest>
diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java
deleted file mode 100644
index c8b075b..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2014 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.test.wakeuploop;
-
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class FileUtil {
-
- private static FileUtil sInst = null;
- private static DateFormat sDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
-
- private FileUtil() {};
-
- public static FileUtil get() {
- if (sInst == null) {
- sInst = new FileUtil();
- }
- return sInst;
- }
-
- public void writeDateToFile(File file) {
- try {
- FileOutputStream fos = new FileOutputStream(file);
- fos.write(sDateFormat.format(new Date()).getBytes());
- fos.write('\n');
- fos.flush();
- fos.close();
- } catch (IOException ioe) {
- Log.e("FileUtil", "exception writing date to file", ioe);
- }
- }
-}
diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java
deleted file mode 100644
index 4f557b8..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2014 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.test.wakeuploop;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.io.File;
-
-public class WakeLoopService extends Service {
-
- private static final String LOG_TAG = WakeLoopService.class.getSimpleName();
- static final String WAKEUP_INTERNAL = "WAKEUP_INTERVAL";
- static final String MAX_LOOP = "MAX_LOOP";
- static final String STOP_CALLBACK = "STOP_CALLBACK";
- static final String THIS_LOOP = "THIS_LOOP";
- static final int MSG_STOP_SERVICE = 0xd1ed1e;
-
- private final Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- if (msg.what == MSG_STOP_SERVICE) {
- stopSelf();
- } else {
- super.handleMessage(msg);
- }
- };
- };
-
- @Override
- public IBinder onBind(Intent intent) {
- // no binding, just start via intent
- return null;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- // get wakeup interval from intent
- long wakeupInterval = intent.getLongExtra(WAKEUP_INTERNAL, 0);
- long maxLoop = intent.getLongExtra(MAX_LOOP, 0);
-
- if (wakeupInterval == 0) {
- // stop and error
- Log.e(LOG_TAG, "No wakeup interval specified, not starting the service");
- stopSelf();
- return START_NOT_STICKY;
- }
- FileUtil.get().writeDateToFile(new File(Environment.getExternalStorageDirectory(),
- "wakeup-loop-start.txt"));
- Log.d(LOG_TAG, String.format("WakeLoop: STARTED interval = %d, total loop = %d",
- wakeupInterval, maxLoop));
- // calculate when device should be waken up
- long atTime = SystemClock.elapsedRealtime() + wakeupInterval;
- AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
- Intent wakupIntent = new Intent(WakeUpCall.WAKEUP_CALL)
- .putExtra(WAKEUP_INTERNAL, wakeupInterval)
- .putExtra(MAX_LOOP, maxLoop)
- .putExtra(THIS_LOOP, 0L)
- .putExtra(STOP_CALLBACK, new Messenger(mHandler));
- PendingIntent pi = PendingIntent.getBroadcast(this, 0, wakupIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- // set alarm, which will be delivered in form of the wakeupIntent
- am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
- return START_NOT_STICKY;
- }
-
- @Override
- public void onDestroy() {
- Log.d(LOG_TAG, "WakeLoop: STOPPED");
- // cancel alarms first
- Intent intent = new Intent(WakeUpCall.WAKEUP_CALL)
- .putExtra(WakeUpCall.CANCEL, "true");
- sendBroadcast(intent);
- }
-}
diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java
deleted file mode 100644
index 8347bbf0..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2014 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.test.wakeuploop;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Environment;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.io.File;
-
-/**
- * The receiver for the alarm we set
- *
- */
-public class WakeUpCall extends BroadcastReceiver {
- private static final String LOG_TAG = WakeUpCall.class.getSimpleName();
- static final String WAKEUP_CALL = "android.test.wakeuploop.WAKEUP";
- static final String CANCEL = "CANCEL";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- boolean cancel = intent.hasExtra(CANCEL);
- if (!cancel) {
- long maxLoop = intent.getLongExtra(WakeLoopService.MAX_LOOP, 0);
- long wakeupInterval = intent.getLongExtra(WakeLoopService.WAKEUP_INTERNAL, 0);
- long thisLoop = intent.getLongExtra(WakeLoopService.THIS_LOOP, -1);
- Log.d(LOG_TAG, String.format("incoming: interval = %d, max loop = %d, this loop = %d",
- wakeupInterval, maxLoop, thisLoop));
- if (thisLoop == -1) {
- Log.e(LOG_TAG, "no valid loop count received, trying to stop service");
- stopService(intent);
- return;
- }
- if (wakeupInterval == 0) {
- Log.e(LOG_TAG, "no valid wakeup interval received, trying to stop service");
- stopService(intent);
- return;
- }
- thisLoop++;
- Log.d(LOG_TAG, String.format("WakeLoop - iteration %d of %d", thisLoop, maxLoop));
- if (thisLoop == maxLoop) {
- // when maxLoop is 0, we loop forever, so not checking that case
- // here
- Log.d(LOG_TAG, "reached max loop count, stopping service");
- stopService(intent);
- return;
- }
- screenOn(context);
- FileUtil.get().writeDateToFile(
- new File(Environment.getExternalStorageDirectory(), "wakeup-loop.txt"));
- // calculate when device should be waken up
- long atTime = SystemClock.elapsedRealtime() + wakeupInterval;
- intent.putExtra(WakeLoopService.THIS_LOOP, thisLoop);
- PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- // set alarm, which will be delivered in form of the wakeupIntent
- am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
- } else {
- // cancel alarms
- Log.d(LOG_TAG, "cancelling future alarms on request");
- am.cancel(PendingIntent.getBroadcast(context, 0, intent, 0));
- }
- }
-
- private void stopService(Intent i) {
- Messenger msgr = i.getParcelableExtra(WakeLoopService.STOP_CALLBACK);
- if (msgr == null) {
- Log.e(LOG_TAG, "no stop service callback found, cannot stop");
- } else {
- Message msg = new Message();
- msg.what = WakeLoopService.MSG_STOP_SERVICE;
- try {
- msgr.send(msg);
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "ignored remoted exception while attempting to stop service", e);
- }
- }
- }
-
- private void screenOn(Context context) {
- PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
- @SuppressWarnings("deprecation")
- WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK |
- PowerManager.ACQUIRE_CAUSES_WAKEUP, LOG_TAG);
- wl.acquire(500);
- }
-}
diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTest.java b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java
new file mode 100644
index 0000000..d0350af
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java
@@ -0,0 +1,338 @@
+/*
+ * 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.test.filters;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+
+/**
+ * JUnit filter to select tests.
+ *
+ * <p>This filter selects tests specified by package name, class name, and method name. With this
+ * filter, the package and the class options of AndroidJUnitRunner can be superseded. Also the
+ * restriction that prevents using the package and the class options can be mitigated.
+ *
+ * <p><b>Select out tests from Java packages:</b> this option supersedes {@code -e package} option.
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.test.filters.SelectTest \
+ * -e selectTest package1.,package2. \
+ * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ * Note that the ending {@code .} in package name is mandatory.
+ *
+ * <p><b>Select out test classes:</b> this option supersedes {@code -e class} option.
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.test.filters.SelectTest \
+ * -e selectTest package1.ClassA,package2.ClassB \
+ * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ *
+ * <p><b>Select out test methods from Java classes:</b>
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.test.filters.SelectTest \
+ * -e selectTest package1.ClassA#methodX,package2.ClassB#methodY \
+ * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ *
+ * Those options can be used simultaneously. For example
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.test.filters.SelectTest \
+ * -e selectTest package1.,package2.classA,package3.ClassB#methodZ \
+ * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ * will select out all tests in package1, all tests in classA, and ClassB#methodZ test.
+ *
+ * <p>Note that when this option is specified with either {@code -e package} or {@code -e class}
+ * option, filtering behaves as logically conjunction. Other options, such as {@code -e notPackage},
+ * {@code -e notClass}, {@code -e annotation}, and {@code -e notAnnotation}, should work as expected
+ * with this SelectTest option.
+ *
+ * <p>When specified with {@code -e selectTest_verbose true} option, {@link SelectTest} verbosely
+ * logs to logcat while parsing {@code -e selectTest} option.
+ */
+public class SelectTest extends Filter {
+
+ private static final String TAG = SelectTest.class.getSimpleName();
+
+ @VisibleForTesting
+ static final String OPTION_SELECT_TEST = "selectTest";
+ @VisibleForTesting
+ static final String OPTION_SELECT_TEST_VERBOSE = OPTION_SELECT_TEST + "_verbose";
+
+ private static final String ARGUMENT_ITEM_SEPARATOR = ",";
+ private static final String PACKAGE_NAME_SEPARATOR = ".";
+ private static final String METHOD_SEPARATOR = "#";
+
+ @Nullable
+ private final PackageSet mPackageSet;
+
+ /**
+ * Construct {@link SelectTest} filter from instrumentation arguments in {@link Bundle}.
+ *
+ * @param testArgs instrumentation test arguments.
+ */
+ public SelectTest(@NonNull Bundle testArgs) {
+ mPackageSet = parseSelectTest(testArgs);
+ }
+
+ @Override
+ public boolean shouldRun(Description description) {
+ if (mPackageSet == null) {
+ // Accept all tests because this filter is disabled.
+ return true;
+ }
+ String testClassName = description.getClassName();
+ String testMethodName = description.getMethodName();
+ return mPackageSet.accept(testClassName, testMethodName);
+ }
+
+ @Override
+ public String describe() {
+ return OPTION_SELECT_TEST + "=" + mPackageSet;
+ }
+
+ /**
+ * Create {@link #OPTION_SELECT_TEST} argument and add it to {@code testArgs}.
+ *
+ * <p>This method is intended to be used at constructor of extended {@link Filter} class.
+ *
+ * @param testArgs instrumentation test arguments.
+ * @param selectTests array of class name to be selected to run.
+ * @return modified instrumentation test arguments.
+ */
+ @NonNull
+ protected static Bundle addSelectTest(
+ @NonNull Bundle testArgs, @NonNull String... selectTests) {
+ if (selectTests.length == 0) {
+ return testArgs;
+ }
+ testArgs.putString(OPTION_SELECT_TEST, join(Arrays.asList(selectTests)));
+ return testArgs;
+ }
+
+ /**
+ * Parse {@code -e selectTest} argument.
+ * @param testArgs instrumentation test arguments.
+ * @return {@link PackageSet} that will filter tests. Returns {@code null} when no
+ * {@code -e selectTest} option is specified, thus this filter gets disabled.
+ */
+ @Nullable
+ private static PackageSet parseSelectTest(Bundle testArgs) {
+ final String selectTestArgs = testArgs.getString(OPTION_SELECT_TEST);
+ if (selectTestArgs == null) {
+ Log.w(TAG, "Disabled because no " + OPTION_SELECT_TEST + " option specified");
+ return null;
+ }
+
+ final boolean verbose = new Boolean(testArgs.getString(OPTION_SELECT_TEST_VERBOSE));
+ final PackageSet packageSet = new PackageSet(verbose);
+ for (String selectTestArg : selectTestArgs.split(ARGUMENT_ITEM_SEPARATOR)) {
+ packageSet.add(selectTestArg);
+ }
+ return packageSet;
+ }
+
+ private static String getPackageName(String selectTestArg) {
+ int endPackagePos = selectTestArg.lastIndexOf(PACKAGE_NAME_SEPARATOR);
+ return (endPackagePos < 0) ? "" : selectTestArg.substring(0, endPackagePos);
+ }
+
+ @Nullable
+ private static String getClassName(String selectTestArg) {
+ if (selectTestArg.endsWith(PACKAGE_NAME_SEPARATOR)) {
+ return null;
+ }
+ int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR);
+ return (methodSepPos < 0) ? selectTestArg : selectTestArg.substring(0, methodSepPos);
+ }
+
+ @Nullable
+ private static String getMethodName(String selectTestArg) {
+ int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR);
+ return (methodSepPos < 0) ? null : selectTestArg.substring(methodSepPos + 1);
+ }
+
+ /** Package level filter */
+ private static class PackageSet {
+ private final boolean mVerbose;
+ /**
+ * Java package name to {@link ClassSet} map. To represent package filtering, a map value
+ * can be {@code null}.
+ */
+ private final Map<String, ClassSet> mClassSetMap = new LinkedHashMap<>();
+
+ PackageSet(boolean verbose) {
+ mVerbose = verbose;
+ }
+
+ void add(final String selectTestArg) {
+ final String packageName = getPackageName(selectTestArg);
+ final String className = getClassName(selectTestArg);
+
+ if (className == null) {
+ ClassSet classSet = mClassSetMap.put(packageName, null); // package filtering.
+ if (mVerbose) {
+ logging("Select package " + selectTestArg, classSet != null,
+ "; supersede " + classSet);
+ }
+ return;
+ }
+
+ ClassSet classSet = mClassSetMap.get(packageName);
+ if (classSet == null) {
+ if (mClassSetMap.containsKey(packageName)) {
+ if (mVerbose) {
+ logging("Select package " + packageName + PACKAGE_NAME_SEPARATOR, true,
+ " ignore " + selectTestArg);
+ }
+ return;
+ }
+ classSet = new ClassSet(mVerbose);
+ mClassSetMap.put(packageName, classSet);
+ }
+ classSet.add(selectTestArg);
+ }
+
+ boolean accept(String className, @Nullable String methodName) {
+ String packageName = getPackageName(className);
+ if (!mClassSetMap.containsKey(packageName)) {
+ return false;
+ }
+ ClassSet classSet = mClassSetMap.get(packageName);
+ return classSet == null || classSet.accept(className, methodName);
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+ for (String packageName : mClassSetMap.keySet()) {
+ ClassSet classSet = mClassSetMap.get(packageName);
+ joiner.add(classSet == null
+ ? packageName + PACKAGE_NAME_SEPARATOR : classSet.toString());
+ }
+ return joiner.toString();
+ }
+ }
+
+ /** Class level filter */
+ private static class ClassSet {
+ private final boolean mVerbose;
+ /**
+ * Java class name to set of method names map. To represent class filtering, a map value
+ * can be {@code null}.
+ */
+ private final Map<String, Set<String>> mMethodSetMap = new LinkedHashMap<>();
+
+ ClassSet(boolean verbose) {
+ mVerbose = verbose;
+ }
+
+ void add(String selectTestArg) {
+ final String className = getClassName(selectTestArg);
+ final String methodName = getMethodName(selectTestArg);
+
+ if (methodName == null) {
+ Set<String> methodSet = mMethodSetMap.put(className, null); // class filtering.
+ if (mVerbose) {
+ logging("Select class " + selectTestArg, methodSet != null,
+ "; supersede " + toString(className, methodSet));
+ }
+ return;
+ }
+
+ Set<String> methodSet = mMethodSetMap.get(className);
+ if (methodSet == null) {
+ if (mMethodSetMap.containsKey(className)) {
+ if (mVerbose) {
+ logging("Select class " + className, true, "; ignore " + selectTestArg);
+ }
+ return;
+ }
+ methodSet = new LinkedHashSet<>();
+ mMethodSetMap.put(className, methodSet);
+ }
+
+ methodSet.add(methodName);
+ if (mVerbose) {
+ logging("Select method " + selectTestArg, false, null);
+ }
+ }
+
+ boolean accept(String className, @Nullable String methodName) {
+ if (!mMethodSetMap.containsKey(className)) {
+ return false;
+ }
+ Set<String> methodSet = mMethodSetMap.get(className);
+ return methodName == null || methodSet == null || methodSet.contains(methodName);
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+ for (String className : mMethodSetMap.keySet()) {
+ joiner.add(toString(className, mMethodSetMap.get(className)));
+ }
+ return joiner.toString();
+ }
+
+ private static String toString(String className, @Nullable Set<String> methodSet) {
+ if (methodSet == null) {
+ return className;
+ }
+ StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+ for (String methodName : methodSet) {
+ joiner.add(className + METHOD_SEPARATOR + methodName);
+ }
+ return joiner.toString();
+ }
+ }
+
+ private static void logging(String infoLog, boolean isWarning, String warningLog) {
+ if (isWarning) {
+ Log.w(TAG, infoLog + warningLog);
+ } else {
+ Log.i(TAG, infoLog);
+ }
+ }
+
+ private static String join(Collection<String> list) {
+ StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+ for (String text : list) {
+ joiner.add(text);
+ }
+ return joiner.toString();
+ }
+}
diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
new file mode 100644
index 0000000..163b00a
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
@@ -0,0 +1,220 @@
+/*
+ * 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.test.filters;
+
+import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST;
+import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST_VERBOSE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Bundle;
+import android.util.ArraySet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.StringJoiner;
+
+public class SelectTestTests {
+
+ private static final String PACKAGE_A = "packageA.";
+ private static final String PACKAGE_B = "packageB.";
+ private static final String PACKAGE_C = "packageC.";
+ private static final String CLASS_A1 = PACKAGE_A + "Class1";
+ private static final String CLASS_A2 = PACKAGE_A + "Class2";
+ private static final String CLASS_B3 = PACKAGE_B + "Class3";
+ private static final String CLASS_B4 = PACKAGE_B + "Class4";
+ private static final String CLASS_C5 = PACKAGE_C + "Class5";
+ private static final String CLASS_C6 = PACKAGE_C + "Class6";
+ private static final String METHOD_A1K = CLASS_A1 + "#methodK";
+ private static final String METHOD_A1L = CLASS_A1 + "#methodL";
+ private static final String METHOD_A2M = CLASS_A2 + "#methodM";
+ private static final String METHOD_A2N = CLASS_A2 + "#methodN";
+ private static final String METHOD_B3P = CLASS_B3 + "#methodP";
+ private static final String METHOD_B3Q = CLASS_B3 + "#methodQ";
+ private static final String METHOD_B4R = CLASS_B4 + "#methodR";
+ private static final String METHOD_B4S = CLASS_B4 + "#methodS";
+ private static final String METHOD_C5W = CLASS_C5 + "#methodW";
+ private static final String METHOD_C5X = CLASS_C5 + "#methodX";
+ private static final String METHOD_C6Y = CLASS_C6 + "#methodY";
+ private static final String METHOD_C6Z = CLASS_C6 + "#methodZ";
+
+ private static final Set<Description> TEST_METHOD_A1K = methodTest(METHOD_A1K);
+ private static final Set<Description> TEST_METHOD_A1L = methodTest(METHOD_A1L);
+ private static final Set<Description> TEST_METHOD_A2M = methodTest(METHOD_A2M);
+ private static final Set<Description> TEST_METHOD_A2N = methodTest(METHOD_A2N);
+ private static final Set<Description> TEST_METHOD_B3P = methodTest(METHOD_B3P);
+ private static final Set<Description> TEST_METHOD_B3Q = methodTest(METHOD_B3Q);
+ private static final Set<Description> TEST_METHOD_B4R = methodTest(METHOD_B4R);
+ private static final Set<Description> TEST_METHOD_B4S = methodTest(METHOD_B4S);
+ private static final Set<Description> TEST_METHOD_C5W = methodTest(METHOD_C5W);
+ private static final Set<Description> TEST_METHOD_C5X = methodTest(METHOD_C5X);
+ private static final Set<Description> TEST_METHOD_C6Y = methodTest(METHOD_C6Y);
+ private static final Set<Description> TEST_METHOD_C6Z = methodTest(METHOD_C6Z);
+ private static final Set<Description> TEST_CLASS_A1 = merge(TEST_METHOD_A1K, TEST_METHOD_A1L);
+ private static final Set<Description> TEST_CLASS_A2 = merge(TEST_METHOD_A2M, TEST_METHOD_A2N);
+ private static final Set<Description> TEST_CLASS_B3 = merge(TEST_METHOD_B3P, TEST_METHOD_B3Q);
+ private static final Set<Description> TEST_CLASS_B4 = merge(TEST_METHOD_B4R, TEST_METHOD_B4S);
+ private static final Set<Description> TEST_CLASS_C5 = merge(TEST_METHOD_C5W, TEST_METHOD_C5X);
+ private static final Set<Description> TEST_CLASS_C6 = merge(TEST_METHOD_C6Y, TEST_METHOD_C6Z);
+ private static final Set<Description> TEST_PACKAGE_A = merge(TEST_CLASS_A1, TEST_CLASS_A2);
+ private static final Set<Description> TEST_PACKAGE_B = merge(TEST_CLASS_B3, TEST_CLASS_B4);
+ private static final Set<Description> TEST_PACKAGE_C = merge(TEST_CLASS_C5, TEST_CLASS_C6);
+ private static final Set<Description> TEST_ALL =
+ merge(TEST_PACKAGE_A, TEST_PACKAGE_B, TEST_PACKAGE_C);
+
+ private SelectTestBuilder mBuilder;
+
+ @Before
+ public void setUp() {
+ mBuilder = new SelectTestBuilder();
+ }
+
+ private static class SelectTestBuilder {
+ private final Bundle mTestArgs = new Bundle();
+
+ Filter build() {
+ mTestArgs.putString(OPTION_SELECT_TEST_VERBOSE, Boolean.TRUE.toString());
+ return new SelectTest(mTestArgs);
+ }
+
+ SelectTestBuilder withSelectTest(String... selectTestArgs) {
+ putTestOption(OPTION_SELECT_TEST, selectTestArgs);
+ return this;
+ }
+
+ private void putTestOption(String option, String... args) {
+ if (args.length > 0) {
+ StringJoiner joiner = new StringJoiner(",");
+ for (String arg : args) {
+ joiner.add(arg);
+ }
+ mTestArgs.putString(option, joiner.toString());
+ }
+ }
+ }
+
+ private static Set<Description> methodTest(String testName) {
+ int methodSep = testName.indexOf("#");
+ String className = testName.substring(0, methodSep);
+ String methodName = testName.substring(methodSep + 1);
+ final Set<Description> tests = new ArraySet<>();
+ tests.add(Description.createSuiteDescription(className));
+ tests.add(Description.createTestDescription(className, methodName));
+ return Collections.unmodifiableSet(tests);
+ }
+
+ @SafeVarargs
+ private static Set<Description> merge(Set<Description>... testSpecs) {
+ final Set<Description> merged = new LinkedHashSet<>();
+ for (Set<Description> testSet : testSpecs) {
+ merged.addAll(testSet);
+ }
+ return Collections.unmodifiableSet(merged);
+ }
+
+ @SafeVarargs
+ private static void acceptTests(Filter filter, Set<Description>... testSpecs) {
+ final Set<Description> accepts = merge(testSpecs);
+ for (Description test : TEST_ALL) {
+ if (accepts.contains(test)) {
+ assertTrue("accept " + test, filter.shouldRun(test));
+ } else {
+ assertFalse("reject " + test, filter.shouldRun(test));
+ }
+ }
+ }
+
+ @Test
+ public void testFilterDisabled() {
+ final Filter filter = mBuilder.build();
+ acceptTests(filter, TEST_ALL);
+ }
+
+ @Test
+ public void testSelectPackage() {
+ final Filter filter = mBuilder.withSelectTest(PACKAGE_A, PACKAGE_B).build();
+ acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B);
+ }
+
+ @Test
+ public void testSelectClass() {
+ final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, CLASS_B3).build();
+ acceptTests(filter, TEST_CLASS_A1, TEST_CLASS_A2, TEST_CLASS_B3);
+ }
+
+ @Test
+ public void testSelectMethod() {
+ final Filter filter = mBuilder
+ .withSelectTest(METHOD_A1K, METHOD_A2M, METHOD_A2N, METHOD_B3P).build();
+ acceptTests(filter, TEST_METHOD_A1K, TEST_METHOD_A2M, TEST_METHOD_A2N, TEST_METHOD_B3P);
+ }
+
+ @Test
+ public void testSelectClassAndPackage() {
+ final Filter filter = mBuilder.withSelectTest(CLASS_A1, PACKAGE_B, CLASS_C5).build();
+ acceptTests(filter, TEST_CLASS_A1, TEST_PACKAGE_B, TEST_CLASS_C5);
+ }
+
+ @Test
+ public void testSelectMethodAndPackage() {
+ final Filter filter = mBuilder.withSelectTest(METHOD_A1K, PACKAGE_B, METHOD_C5W).build();
+ acceptTests(filter, TEST_METHOD_A1K, TEST_PACKAGE_B, TEST_METHOD_C5W);
+ }
+
+ @Test
+ public void testSelectMethodAndClass() {
+ final Filter filter = mBuilder.withSelectTest(METHOD_A1K, CLASS_C5, METHOD_B3P).build();
+ acceptTests(filter, TEST_METHOD_A1K, TEST_CLASS_C5, TEST_METHOD_B3P);
+ }
+
+ @Test
+ public void testSelectClassAndSamePackage() {
+ final Filter filter = mBuilder.withSelectTest(
+ CLASS_A1, PACKAGE_A, CLASS_B3, PACKAGE_C, CLASS_C5).build();
+ acceptTests(filter, TEST_PACKAGE_A, TEST_CLASS_B3, TEST_PACKAGE_C);
+ }
+
+ @Test
+ public void testSelectMethodAndSameClass() {
+ final Filter filter = mBuilder.withSelectTest(
+ METHOD_A1K, METHOD_A2M, CLASS_A1, CLASS_B3, METHOD_B3P, METHOD_B4R).build();
+ acceptTests(filter, TEST_CLASS_A1, TEST_METHOD_A2M, TEST_CLASS_B3, TEST_METHOD_B4R);
+ }
+
+ @Test
+ public void testSelectMethodAndSamePackage() {
+ final Filter filter = mBuilder.withSelectTest(
+ METHOD_A1K, METHOD_A1L, METHOD_A2M, PACKAGE_A,
+ PACKAGE_C, METHOD_C5W, METHOD_C5X, METHOD_C6Y).build();
+ acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C);
+ }
+
+ @Test
+ public void testSelectMethodAndClassAndPackage() {
+ final Filter filter = mBuilder.withSelectTest(
+ METHOD_A1K, CLASS_A1, METHOD_A1L, METHOD_A2M, PACKAGE_A,
+ PACKAGE_B, METHOD_B3Q, CLASS_B3, METHOD_B4R, METHOD_B3P).build();
+ acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B);
+ }
+}
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 92beb4e..0512bdc 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -469,16 +469,12 @@
return false;
}
- // Read the file as a string
- char buffer_2[data->size()];
- memcpy(&buffer_2, data->data(), data->size());
- StringPiece content(buffer_2, data->size());
-
BigBuffer crunched_png_buffer(4096);
io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
// Ensure that we only keep the chunks we care about if we end up
// using the original PNG instead of the crunched one.
+ const StringPiece content(reinterpret_cast<const char*>(data->data()), data->size());
PngChunkFilter png_chunk_filter(content);
std::unique_ptr<Image> image = ReadPng(context, path_data.source, &png_chunk_filter);
if (!image) {
diff --git a/tools/aapt2/cmd/Convert_test.cpp b/tools/aapt2/cmd/Convert_test.cpp
index 2e43150..8da5bb8 100644
--- a/tools/aapt2/cmd/Convert_test.cpp
+++ b/tools/aapt2/cmd/Convert_test.cpp
@@ -53,7 +53,11 @@
// Load the binary xml tree
android::ResXMLTree tree;
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_convert_apk, &diag);
- AssertLoadXml(apk.get(), "res/xml/test.xml", &tree);
+
+ std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
+ ASSERT_THAT(data, Ne(nullptr));
+
+ AssertLoadXml(apk.get(), data.get(), &tree);
// Check that the raw string index has not been assigned
EXPECT_THAT(tree.getAttributeValueStringID(0), Eq(-1));
@@ -87,7 +91,11 @@
// Load the binary xml tree
android::ResXMLTree tree;
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_convert_apk, &diag);
- AssertLoadXml(apk.get(), "res/xml/test.xml", &tree);
+
+ std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
+ ASSERT_THAT(data, Ne(nullptr));
+
+ AssertLoadXml(apk.get(), data.get(), &tree);
// Check that the raw string index has been set to the correct string pool entry
int32_t raw_index = tree.getAttributeValueStringID(0);
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 3c8b72d..9ea93f6 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -43,7 +43,11 @@
// Load the binary xml tree
android::ResXMLTree tree;
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
- AssertLoadXml(apk.get(), "res/xml/test.xml", &tree);
+
+ std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
+ ASSERT_THAT(data, Ne(nullptr));
+
+ AssertLoadXml(apk.get(), data.get(), &tree);
// Check that the raw string index has not been assigned
EXPECT_THAT(tree.getAttributeValueStringID(0), Eq(-1));
@@ -67,7 +71,11 @@
// Load the binary xml tree
android::ResXMLTree tree;
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
- AssertLoadXml(apk.get(), "res/xml/test.xml", &tree);
+
+ std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
+ ASSERT_THAT(data, Ne(nullptr));
+
+ AssertLoadXml(apk.get(), data.get(), &tree);
// Check that the raw string index has been set to the correct string pool entry
int32_t raw_index = tree.getAttributeValueStringID(0);
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index aae79fa..3fcdfb7 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -133,16 +133,18 @@
return manifest_file;
}
-void CommandTestFixture::AssertLoadXml(LoadedApk *apk, const android::StringPiece &xml_path,
+std::unique_ptr<io::IData> CommandTestFixture::OpenFileAsData(LoadedApk* apk,
+ const android::StringPiece& path) {
+ return apk
+ ->GetFileCollection()
+ ->FindFile(path)
+ ->OpenAsData();
+}
+
+void CommandTestFixture::AssertLoadXml(LoadedApk* apk, const io::IData* data,
android::ResXMLTree *out_tree) {
ASSERT_THAT(apk, Ne(nullptr));
- io::IFile* file = apk->GetFileCollection()->FindFile(xml_path);
- ASSERT_THAT(file, Ne(nullptr));
-
- std::unique_ptr<io::IData> data = file->OpenAsData();
- ASSERT_THAT(data, Ne(nullptr));
-
out_tree->setTo(data->data(), data->size());
ASSERT_THAT(out_tree->getError(), Eq(android::OK));
while (out_tree->next() != android::ResXMLTree::START_TAG) {
diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h
index 89d3b7b..3079c75 100644
--- a/tools/aapt2/test/Fixture.h
+++ b/tools/aapt2/test/Fixture.h
@@ -83,8 +83,12 @@
// Creates a minimal android manifest within the test directory and returns the file path.
std::string GetDefaultManifest();
+ // Returns pointer to data inside APK files
+ std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk,
+ const android::StringPiece& path);
+
// Asserts that loading the tree from the specified file in the apk succeeds.
- void AssertLoadXml(LoadedApk* apk, const android::StringPiece& xml_path,
+ void AssertLoadXml(LoadedApk* apk, const io::IData* data,
android::ResXMLTree* out_tree);
private:
diff --git a/tools/processors/unsupportedappusage/Android.bp b/tools/processors/unsupportedappusage/Android.bp
index 1aca3ed..0e33fdd 100644
--- a/tools/processors/unsupportedappusage/Android.bp
+++ b/tools/processors/unsupportedappusage/Android.bp
@@ -1,6 +1,8 @@
-java_library_host {
+java_plugin {
name: "unsupportedappusage-annotation-processor",
+ processor_class: "android.processor.unsupportedappusage.UnsupportedAppUsageProcessor",
+
java_resources: [
"META-INF/**/*",
],
diff --git a/tools/signedconfig/debug_key.pem b/tools/signedconfig/debug_key.pem
index 0af577b..17a1dff 100644
--- a/tools/signedconfig/debug_key.pem
+++ b/tools/signedconfig/debug_key.pem
@@ -1,5 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
-MHcCAQEEIEfgtO+KPOoqJqTnqkDDKkAcOzyvtovsUO/ShLE6y4XRoAoGCCqGSM49
-AwEHoUQDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60pj1pnU8
-SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==
+MHcCAQEEIFbNNr1/TsFlvnmH1z6e0xyact9t7PDs+VFWc7QFtoRcoAoGCCqGSM49
+AwEHoUQDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13YrU1haIhVC5296InRu1x7A8PV1
+ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg==
-----END EC PRIVATE KEY-----
diff --git a/tools/signedconfig/debug_public.pem b/tools/signedconfig/debug_public.pem
index f61f813..d9f0d38 100644
--- a/tools/signedconfig/debug_public.pem
+++ b/tools/signedconfig/debug_public.pem
@@ -1,4 +1,4 @@
-----BEGIN PUBLIC KEY-----
-MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoE
-CGbTEBTKKvdd2hO60pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13Yr
+U1haIhVC5296InRu1x7A8PV1ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg==
-----END PUBLIC KEY-----
diff --git a/tools/signedconfig/debug_sign.sh b/tools/signedconfig/debug_sign.sh
index 28e5428..3a2814a 100755
--- a/tools/signedconfig/debug_sign.sh
+++ b/tools/signedconfig/debug_sign.sh
@@ -2,5 +2,5 @@
# Script to sign data with the debug keys. Outputs base64 for embedding into
# APK metadata.
-openssl dgst -sha256 -sign $(dirname $0)/debug_key.pem $1 | base64 -w 0
+openssl dgst -sha256 -sign $(dirname $0)/debug_key.pem <(echo -n "$1" | base64 -d) | base64 -w 0
echo
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 46c4191..a0ce9dd 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -28,6 +28,7 @@
import android.net.wifi.INetworkRequestMatchCallback;
import android.net.wifi.ISoftApCallback;
import android.net.wifi.ITrafficStateCallback;
+import android.net.wifi.IWifiUsabilityStatsListener;
import android.net.wifi.PasspointManagementObjectDefinition;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiActivityEnergyInfo;
@@ -186,6 +187,10 @@
void unregisterSoftApCallback(int callbackIdentifier);
+ void addWifiUsabilityStatsListener(in IBinder binder, in IWifiUsabilityStatsListener listener, int listenerIdentifier);
+
+ void removeWifiUsabilityStatsListener(int listenerIdentifier);
+
void registerTrafficStateCallback(in IBinder binder, in ITrafficStateCallback callback, int callbackIdentifier);
void unregisterTrafficStateCallback(int callbackIdentifier);
@@ -210,4 +215,3 @@
void stopDppSession();
}
-
diff --git a/wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl b/wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl
new file mode 100644
index 0000000..284ffaa
--- /dev/null
+++ b/wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.net.wifi;
+
+import android.net.wifi.WifiUsabilityStatsEntry;
+
+/**
+ * Interface for Wi-Fi usability stats listener.
+ *
+ * @hide
+ */
+oneway interface IWifiUsabilityStatsListener
+{
+ /**
+ * Service to manager callback providing current Wi-Fi usability stats.
+ *
+ * @param seqNum The sequence number of stats, used to derive the timing of updated Wi-Fi
+ * usability statistics, set by framework and shall be incremented by one
+ * after each update.
+ * @param isSameBssidAndFreq The flag to indicate whether the BSSID and the frequency of
+ * network stays the same or not relative to the last update of
+ * Wi-Fi usability stats.
+ * @param stats The updated Wi-Fi usability statistics.
+ */
+ void onStatsUpdated(int seqNum, boolean isSameBssidAndFreq,
+ in WifiUsabilityStatsEntry stats);
+}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 8c00ceb..084bd09 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -2084,6 +2084,8 @@
public static final int WIFI_FEATURE_LOW_LATENCY = 0x40000000; // Low Latency modes
/** @hide */
public static final int WIFI_FEATURE_DPP = 0x80000000; // DPP (Easy-Connect)
+ /** @hide */
+ public static final long WIFI_FEATURE_P2P_RAND_MAC = 0x100000000L; // Random P2P MAC
private long getSupportedFeatures() {
try {
@@ -3717,10 +3719,8 @@
* @param SSID, in the format of WifiConfiguration's SSID.
* @hide
*/
- @SystemApi
@RequiresPermission(anyOf = {
android.Manifest.permission.NETWORK_SETTINGS,
- android.Manifest.permission.NETWORK_SETUP_WIZARD,
android.Manifest.permission.NETWORK_STACK
})
public void disableEphemeralNetwork(String SSID) {
@@ -4777,4 +4777,92 @@
});
}
}
+
+ /**
+ * Interface for Wi-Fi usability statistics listener. Should be implemented by applications and
+ * set when calling {@link WifiManager#addWifiUsabilityStatsListener(Executor,
+ * WifiUsabilityStatsListener)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface WifiUsabilityStatsListener {
+ /**
+ * Called when Wi-Fi usability statistics is updated.
+ *
+ * @param seqNum The sequence number of statistics, used to derive the timing of updated
+ * Wi-Fi usability statistics, set by framework and incremented by one after
+ * each update.
+ * @param isSameBssidAndFreq The flag to indicate whether the BSSID and the frequency of
+ * network stays the same or not relative to the last update of
+ * Wi-Fi usability stats.
+ * @param stats The updated Wi-Fi usability statistics.
+ */
+ void onStatsUpdated(int seqNum, boolean isSameBssidAndFreq,
+ WifiUsabilityStatsEntry stats);
+ }
+
+ /**
+ * Adds a listener for Wi-Fi usability statistics. See {@link WifiUsabilityStatsListener}.
+ * Multiple listeners can be added. Callers will be invoked periodically by framework to
+ * inform clients about the current Wi-Fi usability statistics. Callers can remove a previously
+ * added listener using {@link removeWifiUsabilityStatsListener}.
+ *
+ * @param executor The executor on which callback will be invoked.
+ * @param listener Listener for Wifi usability statistics.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE)
+ public void addWifiUsabilityStatsListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull WifiUsabilityStatsListener listener) {
+ if (executor == null) throw new IllegalArgumentException("executor cannot be null");
+ if (listener == null) throw new IllegalArgumentException("listener cannot be null");
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "addWifiUsabilityStatsListener: listener=" + listener);
+ }
+ try {
+ mService.addWifiUsabilityStatsListener(new Binder(),
+ new IWifiUsabilityStatsListener.Stub() {
+ @Override
+ public void onStatsUpdated(int seqNum, boolean isSameBssidAndFreq,
+ WifiUsabilityStatsEntry stats) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "WifiUsabilityStatsListener: onStatsUpdated: seqNum="
+ + seqNum);
+ }
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> listener.onStatsUpdated(seqNum,
+ isSameBssidAndFreq, stats)));
+ }
+ },
+ listener.hashCode()
+ );
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Allow callers to remove a previously registered listener. After calling this method,
+ * applications will no longer receive Wi-Fi usability statistics.
+ *
+ * @param listener Listener to remove the Wi-Fi usability statistics.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE)
+ public void removeWifiUsabilityStatsListener(@NonNull WifiUsabilityStatsListener listener) {
+ if (listener == null) throw new IllegalArgumentException("listener cannot be null");
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "removeWifiUsabilityStatsListener: listener=" + listener);
+ }
+ try {
+ mService.removeWifiUsabilityStatsListener(listener.hashCode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
index aa1669e..52ee742 100644
--- a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
@@ -28,6 +28,7 @@
import android.net.NetworkSpecifier;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import java.util.Objects;
@@ -50,12 +51,24 @@
*/
private final int mOriginalRequestorUid;
+ /**
+ * The package name of the app that requested a specific wifi network using
+ * {@link WifiNetworkSpecifier}.
+ *
+ * Will only be filled when the device connects to a wifi network as a result of a
+ * {@link NetworkRequest} with {@link WifiNetworkSpecifier}. Will be set to null if the device
+ * auto-connected to a wifi network.
+ */
+ private final String mOriginalRequestorPackageName;
+
public WifiNetworkAgentSpecifier(@NonNull WifiConfiguration wifiConfiguration,
- int originalRequestorUid) {
+ int originalRequestorUid,
+ @Nullable String originalRequestorPackageName) {
checkNotNull(wifiConfiguration);
mWifiConfiguration = wifiConfiguration;
mOriginalRequestorUid = originalRequestorUid;
+ mOriginalRequestorPackageName = originalRequestorPackageName;
}
/**
@@ -67,7 +80,9 @@
public WifiNetworkAgentSpecifier createFromParcel(@NonNull Parcel in) {
WifiConfiguration wifiConfiguration = in.readParcelable(null);
int originalRequestorUid = in.readInt();
- return new WifiNetworkAgentSpecifier(wifiConfiguration, originalRequestorUid);
+ String originalRequestorPackageName = in.readString();
+ return new WifiNetworkAgentSpecifier(
+ wifiConfiguration, originalRequestorUid, originalRequestorPackageName);
}
@Override
@@ -85,6 +100,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelable(mWifiConfiguration, flags);
dest.writeInt(mOriginalRequestorUid);
+ dest.writeString(mOriginalRequestorPackageName);
}
@Override
@@ -137,6 +153,9 @@
if (ns.requestorUid != this.mOriginalRequestorUid) {
return false;
}
+ if (!TextUtils.equals(ns.requestorPackageName, this.mOriginalRequestorPackageName)) {
+ return false;
+ }
return true;
}
@@ -146,7 +165,8 @@
mWifiConfiguration.SSID,
mWifiConfiguration.BSSID,
mWifiConfiguration.allowedKeyManagement,
- mOriginalRequestorUid);
+ mOriginalRequestorUid,
+ mOriginalRequestorPackageName);
}
@Override
@@ -162,7 +182,9 @@
&& Objects.equals(this.mWifiConfiguration.BSSID, lhs.mWifiConfiguration.BSSID)
&& Objects.equals(this.mWifiConfiguration.allowedKeyManagement,
lhs.mWifiConfiguration.allowedKeyManagement)
- && mOriginalRequestorUid == lhs.mOriginalRequestorUid;
+ && mOriginalRequestorUid == lhs.mOriginalRequestorUid
+ && TextUtils.equals(mOriginalRequestorPackageName,
+ lhs.mOriginalRequestorPackageName);
}
@Override
@@ -172,6 +194,7 @@
.append(", SSID=").append(mWifiConfiguration.SSID)
.append(", BSSID=").append(mWifiConfiguration.BSSID)
.append(", mOriginalRequestorUid=").append(mOriginalRequestorUid)
+ .append(", mOriginalRequestorPackageName=").append(mOriginalRequestorPackageName)
.append("]");
return sb.toString();
}
diff --git a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
index ecee5ff..42d4393 100644
--- a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
+++ b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.net.MacAddress;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
@@ -586,7 +587,8 @@
mSsidPatternMatcher,
mBssidPatternMatcher,
buildWifiConfiguration(),
- Process.myUid());
+ Process.myUid(),
+ ActivityThread.currentApplication().getApplicationContext().getOpPackageName());
}
/**
@@ -648,7 +650,8 @@
buildWifiConfiguration(),
mIsAppInteractionRequired,
mIsUserInteractionRequired,
- Process.myUid());
+ Process.myUid(),
+ ActivityThread.currentApplication().getApplicationContext().getOpPackageName());
}
}
diff --git a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
index 6e4eeef..a5f4675 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
@@ -25,6 +25,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PatternMatcher;
+import android.text.TextUtils;
import android.util.Pair;
import java.util.Objects;
@@ -63,18 +64,25 @@
*/
public final int requestorUid;
+ /**
+ * The package name of the app initializing this network specifier.
+ */
+ public final String requestorPackageName;
+
public WifiNetworkSpecifier(@NonNull PatternMatcher ssidPatternMatcher,
@NonNull Pair<MacAddress, MacAddress> bssidPatternMatcher,
@NonNull WifiConfiguration wifiConfiguration,
- int requestorUid) {
+ int requestorUid, @NonNull String requestorPackageName) {
checkNotNull(ssidPatternMatcher);
checkNotNull(bssidPatternMatcher);
checkNotNull(wifiConfiguration);
+ checkNotNull(requestorPackageName);
this.ssidPatternMatcher = ssidPatternMatcher;
this.bssidPatternMatcher = bssidPatternMatcher;
this.wifiConfiguration = wifiConfiguration;
this.requestorUid = requestorUid;
+ this.requestorPackageName = requestorPackageName;
}
public static final Creator<WifiNetworkSpecifier> CREATOR =
@@ -88,8 +96,9 @@
Pair.create(baseAddress, mask);
WifiConfiguration wifiConfiguration = in.readParcelable(null);
int requestorUid = in.readInt();
+ String requestorPackageName = in.readString();
return new WifiNetworkSpecifier(ssidPatternMatcher, bssidPatternMatcher,
- wifiConfiguration, requestorUid);
+ wifiConfiguration, requestorUid, requestorPackageName);
}
@Override
@@ -110,6 +119,7 @@
dest.writeParcelable(bssidPatternMatcher.second, flags);
dest.writeParcelable(wifiConfiguration, flags);
dest.writeInt(requestorUid);
+ dest.writeString(requestorPackageName);
}
@Override
@@ -136,7 +146,7 @@
ssidPatternMatcher.getType(),
bssidPatternMatcher,
wifiConfiguration.allowedKeyManagement,
- requestorUid);
+ requestorUid, requestorPackageName);
}
@Override
@@ -156,7 +166,8 @@
lhs.bssidPatternMatcher)
&& Objects.equals(this.wifiConfiguration.allowedKeyManagement,
lhs.wifiConfiguration.allowedKeyManagement)
- && requestorUid == lhs.requestorUid;
+ && requestorUid == lhs.requestorUid
+ && TextUtils.equals(requestorPackageName, lhs.requestorPackageName);
}
@Override
@@ -168,6 +179,7 @@
.append(", SSID=").append(wifiConfiguration.SSID)
.append(", BSSID=").append(wifiConfiguration.BSSID)
.append(", requestorUid=").append(requestorUid)
+ .append(", requestorPackageName=").append(requestorPackageName)
.append("]")
.toString();
}
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 3c90eb7..6b05dfc 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -18,8 +18,10 @@
import static com.android.internal.util.Preconditions.checkNotNull;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import java.util.List;
import java.util.Objects;
@@ -58,17 +60,25 @@
*/
public final int suggestorUid;
+ /**
+ * The package name of the process initializing this network suggestion.
+ * @hide
+ */
+ public final String suggestorPackageName;
+
/** @hide */
- public WifiNetworkSuggestion(WifiConfiguration wifiConfiguration,
+ public WifiNetworkSuggestion(@NonNull WifiConfiguration wifiConfiguration,
boolean isAppInteractionRequired,
boolean isUserInteractionRequired,
- int suggestorUid) {
+ int suggestorUid, @NonNull String suggestorPackageName) {
checkNotNull(wifiConfiguration);
+ checkNotNull(suggestorPackageName);
this.wifiConfiguration = wifiConfiguration;
this.isAppInteractionRequired = isAppInteractionRequired;
this.isUserInteractionRequired = isUserInteractionRequired;
this.suggestorUid = suggestorUid;
+ this.suggestorPackageName = suggestorPackageName;
}
public static final Creator<WifiNetworkSuggestion> CREATOR =
@@ -79,7 +89,8 @@
in.readParcelable(null), // wifiConfiguration
in.readBoolean(), // isAppInteractionRequired
in.readBoolean(), // isUserInteractionRequired
- in.readInt() // suggestorUid
+ in.readInt(), // suggestorUid
+ in.readString() // suggestorPackageName
);
}
@@ -100,12 +111,13 @@
dest.writeBoolean(isAppInteractionRequired);
dest.writeBoolean(isUserInteractionRequired);
dest.writeInt(suggestorUid);
+ dest.writeString(suggestorPackageName);
}
@Override
public int hashCode() {
return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID,
- wifiConfiguration.allowedKeyManagement, suggestorUid);
+ wifiConfiguration.allowedKeyManagement, suggestorUid, suggestorPackageName);
}
/**
@@ -124,7 +136,8 @@
&& Objects.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID)
&& Objects.equals(this.wifiConfiguration.allowedKeyManagement,
lhs.wifiConfiguration.allowedKeyManagement)
- && suggestorUid == lhs.suggestorUid;
+ && suggestorUid == lhs.suggestorUid
+ && TextUtils.equals(suggestorPackageName, lhs.suggestorPackageName);
}
@Override
@@ -135,6 +148,7 @@
.append(", isAppInteractionRequired=").append(isAppInteractionRequired)
.append(", isUserInteractionRequired=").append(isUserInteractionRequired)
.append(", suggestorUid=").append(suggestorUid)
+ .append(", suggestorPackageName=").append(suggestorPackageName)
.append("]");
return sb.toString();
}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
similarity index 73%
copy from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
copy to wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
index 62a8c48..839af54 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 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,10 +14,6 @@
* limitations under the License.
*/
-package com.android.testing.alarmservice;
+package android.net.wifi;
-interface Alarm {
- int prepare();
- int setAlarmAndWait(long timeoutMills);
- int done();
-}
\ No newline at end of file
+parcelable WifiUsabilityStatsEntry;
diff --git a/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.java b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.java
new file mode 100644
index 0000000..c796e29
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.java
@@ -0,0 +1,148 @@
+/*
+ * 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.net.wifi;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class makes a subset of
+ * com.android.server.wifi.nano.WifiMetricsProto.WifiUsabilityStatsEntry parcelable.
+ *
+ * @hide
+ */
+@SystemApi
+public final class WifiUsabilityStatsEntry implements Parcelable {
+ /** Absolute milliseconds from device boot when these stats were sampled */
+ public final long timeStampMs;
+ /** The RSSI (in dBm) at the sample time */
+ public final int rssi;
+ /** Link speed at the sample time in Mbps */
+ public final int linkSpeedMbps;
+ /** The total number of tx success counted from the last radio chip reset */
+ public final long totalTxSuccess;
+ /** The total number of MPDU data packet retries counted from the last radio chip reset */
+ public final long totalTxRetries;
+ /** The total number of tx bad counted from the last radio chip reset */
+ public final long totalTxBad;
+ /** The total number of rx success counted from the last radio chip reset */
+ public final long totalRxSuccess;
+ /** The total time the wifi radio is on in ms counted from the last radio chip reset */
+ public final long totalRadioOnTimeMs;
+ /** The total time the wifi radio is doing tx in ms counted from the last radio chip reset */
+ public final long totalRadioTxTimeMs;
+ /** The total time the wifi radio is doing rx in ms counted from the last radio chip reset */
+ public final long totalRadioRxTimeMs;
+ /** The total time spent on all types of scans in ms counted from the last radio chip reset */
+ public final long totalScanTimeMs;
+ /** The total time spent on nan scans in ms counted from the last radio chip reset */
+ public final long totalNanScanTimeMs;
+ /** The total time spent on background scans in ms counted from the last radio chip reset */
+ public final long totalBackgroundScanTimeMs;
+ /** The total time spent on roam scans in ms counted from the last radio chip reset */
+ public final long totalRoamScanTimeMs;
+ /** The total time spent on pno scans in ms counted from the last radio chip reset */
+ public final long totalPnoScanTimeMs;
+ /** The total time spent on hotspot2.0 scans and GAS exchange in ms counted from the last radio
+ * chip reset */
+ public final long totalHotspot2ScanTimeMs;
+ /** The total time CCA is on busy status on the current frequency in ms counted from the last
+ * radio chip reset */
+ public final long totalCcaBusyFreqTimeMs;
+ /** The total radio on time of the current frequency from the last radio chip reset */
+ public final long totalRadioOnFreqTimeMs;
+ /** The total number of beacons received from the last radio chip reset */
+ public final long totalBeaconRx;
+
+ /** Constructor function {@hide} */
+ public WifiUsabilityStatsEntry(long timeStampMs, int rssi,
+ int linkSpeedMbps, long totalTxSuccess, long totalTxRetries,
+ long totalTxBad, long totalRxSuccess, long totalRadioOnTimeMs,
+ long totalRadioTxTimeMs, long totalRadioRxTimeMs, long totalScanTimeMs,
+ long totalNanScanTimeMs, long totalBackgroundScanTimeMs, long totalRoamScanTimeMs,
+ long totalPnoScanTimeMs, long totalHotspot2ScanTimeMs, long totalCcaBusyFreqTimeMs,
+ long totalRadioOnFreqTimeMs, long totalBeaconRx) {
+ this.timeStampMs = timeStampMs;
+ this.rssi = rssi;
+ this.linkSpeedMbps = linkSpeedMbps;
+ this.totalTxSuccess = totalTxSuccess;
+ this.totalTxRetries = totalTxRetries;
+ this.totalTxBad = totalTxBad;
+ this.totalRxSuccess = totalRxSuccess;
+ this.totalRadioOnTimeMs = totalRadioOnTimeMs;
+ this.totalRadioTxTimeMs = totalRadioTxTimeMs;
+ this.totalRadioRxTimeMs = totalRadioRxTimeMs;
+ this.totalScanTimeMs = totalScanTimeMs;
+ this.totalNanScanTimeMs = totalNanScanTimeMs;
+ this.totalBackgroundScanTimeMs = totalBackgroundScanTimeMs;
+ this.totalRoamScanTimeMs = totalRoamScanTimeMs;
+ this.totalPnoScanTimeMs = totalPnoScanTimeMs;
+ this.totalHotspot2ScanTimeMs = totalHotspot2ScanTimeMs;
+ this.totalCcaBusyFreqTimeMs = totalCcaBusyFreqTimeMs;
+ this.totalRadioOnFreqTimeMs = totalRadioOnFreqTimeMs;
+ this.totalBeaconRx = totalBeaconRx;
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(timeStampMs);
+ dest.writeInt(rssi);
+ dest.writeInt(linkSpeedMbps);
+ dest.writeLong(totalTxSuccess);
+ dest.writeLong(totalTxRetries);
+ dest.writeLong(totalTxBad);
+ dest.writeLong(totalRxSuccess);
+ dest.writeLong(totalRadioOnTimeMs);
+ dest.writeLong(totalRadioTxTimeMs);
+ dest.writeLong(totalRadioRxTimeMs);
+ dest.writeLong(totalScanTimeMs);
+ dest.writeLong(totalNanScanTimeMs);
+ dest.writeLong(totalBackgroundScanTimeMs);
+ dest.writeLong(totalRoamScanTimeMs);
+ dest.writeLong(totalPnoScanTimeMs);
+ dest.writeLong(totalHotspot2ScanTimeMs);
+ dest.writeLong(totalCcaBusyFreqTimeMs);
+ dest.writeLong(totalRadioOnFreqTimeMs);
+ dest.writeLong(totalBeaconRx);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final Creator<WifiUsabilityStatsEntry> CREATOR =
+ new Creator<WifiUsabilityStatsEntry>() {
+ public WifiUsabilityStatsEntry createFromParcel(Parcel in) {
+ return new WifiUsabilityStatsEntry(
+ in.readLong(), in.readInt(),
+ in.readInt(), in.readLong(), in.readLong(),
+ in.readLong(), in.readLong(), in.readLong(),
+ in.readLong(), in.readLong(), in.readLong(),
+ in.readLong(), in.readLong(), in.readLong(),
+ in.readLong(), in.readLong(), in.readLong(),
+ in.readLong(), in.readLong()
+ );
+ }
+
+ public WifiUsabilityStatsEntry[] newArray(int size) {
+ return new WifiUsabilityStatsEntry[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
index 1ee874a..1d499b6 100644
--- a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
+++ b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
@@ -65,9 +65,9 @@
public static final int OSU_FAILURE_PROVISIONING_NOT_AVAILABLE = 7;
/**
- * The reason code for provisioning failure due to invalid server url.
+ * The reason code for provisioning failure due to invalid web url format for an OSU web page.
*/
- public static final int OSU_FAILURE_INVALID_SERVER_URL = 8;
+ public static final int OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU = 8;
/**
* The reason code for provisioning failure when a command received is not the expected command
diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index b3ac9f1..226c7c48 100644
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -26,6 +26,7 @@
import android.net.wifi.ISoftApCallback;
import android.net.wifi.ITrafficStateCallback;
import android.net.wifi.IWifiManager;
+import android.net.wifi.IWifiUsabilityStatsListener;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiActivityEnergyInfo;
import android.net.wifi.WifiConfiguration;
@@ -464,4 +465,15 @@
public void stopDppSession() throws RemoteException {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public void addWifiUsabilityStatsListener(
+ IBinder binder, IWifiUsabilityStatsListener listener, int listenerIdentifier) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeWifiUsabilityStatsListener(int listenerIdentifier) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 4fbef5a..5c2f626 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -61,6 +61,7 @@
import android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback;
import android.net.wifi.WifiManager.SoftApCallback;
import android.net.wifi.WifiManager.TrafficStateCallback;
+import android.net.wifi.WifiManager.WifiUsabilityStatsListener;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -80,6 +81,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
/**
* Unit tests for {@link android.net.wifi.WifiManager}.
@@ -103,7 +105,9 @@
@Mock SoftApCallback mSoftApCallback;
@Mock TrafficStateCallback mTrafficStateCallback;
@Mock NetworkRequestMatchCallback mNetworkRequestMatchCallback;
+ @Mock WifiUsabilityStatsListener mWifiUsabilityStatsListener;
+ private Executor mExecutor;
private Handler mHandler;
private TestLooper mLooper;
private WifiManager mWifiManager;
@@ -1342,4 +1346,40 @@
assertArrayEquals(TEST_MAC_ADDRESSES, mWifiManager.getFactoryMacAddresses());
verify(mWifiService).getFactoryMacAddresses();
}
+
+ /**
+ * Verify the call to addWifiUsabilityStatsListener goes to WifiServiceImpl.
+ */
+ @Test
+ public void addWifiUsabilityStatsListeneroesToWifiServiceImpl() throws Exception {
+ mExecutor = new SynchronousExecutor();
+ mWifiManager.addWifiUsabilityStatsListener(mExecutor, mWifiUsabilityStatsListener);
+ verify(mWifiService).addWifiUsabilityStatsListener(any(IBinder.class),
+ any(IWifiUsabilityStatsListener.Stub.class), anyInt());
+ }
+
+ /**
+ * Verify the call to removeWifiUsabilityStatsListener goes to WifiServiceImpl.
+ */
+ @Test
+ public void removeWifiUsabilityListenerGoesToWifiServiceImpl() throws Exception {
+ ArgumentCaptor<Integer> listenerIdentifier = ArgumentCaptor.forClass(Integer.class);
+ mExecutor = new SynchronousExecutor();
+ mWifiManager.addWifiUsabilityStatsListener(mExecutor, mWifiUsabilityStatsListener);
+ verify(mWifiService).addWifiUsabilityStatsListener(any(IBinder.class),
+ any(IWifiUsabilityStatsListener.Stub.class), listenerIdentifier.capture());
+
+ mWifiManager.removeWifiUsabilityStatsListener(mWifiUsabilityStatsListener);
+ verify(mWifiService).removeWifiUsabilityStatsListener(
+ eq((int) listenerIdentifier.getValue()));
+ }
+
+ /**
+ * Defined for testing purpose.
+ */
+ class SynchronousExecutor implements Executor {
+ public void execute(Runnable r) {
+ r.run();
+ }
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
index 2258e4d..e6eece8 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
@@ -38,6 +38,8 @@
public class WifiNetworkAgentSpecifierTest {
private static final int TEST_UID = 5;
private static final int TEST_UID_1 = 8;
+ private static final String TEST_PACKAGE = "com.test";
+ private static final String TEST_PACKAGE_1 = "com.test.1";
private static final String TEST_SSID = "Test123";
private static final String TEST_SSID_PATTERN = "Test";
private static final String TEST_SSID_1 = "456test";
@@ -104,14 +106,14 @@
WifiNetworkAgentSpecifier specifier1 =
new WifiNetworkAgentSpecifier(
wifiConfiguration1,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkAgentSpecifier specifier2 =
new WifiNetworkAgentSpecifier(
wifiConfiguration2,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(specifier2.equals(specifier1));
}
@@ -128,14 +130,14 @@
WifiNetworkAgentSpecifier specifier1 =
new WifiNetworkAgentSpecifier(
wifiConfiguration1,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
wifiConfiguration2.SSID = TEST_SSID_1;
WifiNetworkAgentSpecifier specifier2 =
new WifiNetworkAgentSpecifier(
wifiConfiguration2,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(specifier2.equals(specifier1));
}
@@ -152,14 +154,14 @@
WifiNetworkAgentSpecifier specifier1 =
new WifiNetworkAgentSpecifier(
wifiConfiguration1,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
wifiConfiguration2.BSSID = TEST_BSSID_1;
WifiNetworkAgentSpecifier specifier2 =
new WifiNetworkAgentSpecifier(
wifiConfiguration2,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(specifier2.equals(specifier1));
}
@@ -214,7 +216,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -243,7 +245,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -272,7 +274,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -292,7 +294,7 @@
WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
new WifiNetworkAgentSpecifier(
wifiConfigurationNetworkAgent,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
PatternMatcher ssidPattern =
new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX);
@@ -305,7 +307,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -325,7 +327,7 @@
WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
new WifiNetworkAgentSpecifier(
wifiConfigurationNetworkAgent,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
PatternMatcher ssidPattern =
new PatternMatcher(".*", PatternMatcher.PATTERN_SIMPLE_GLOB);
@@ -339,7 +341,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -359,7 +361,7 @@
WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
new WifiNetworkAgentSpecifier(
wifiConfigurationNetworkAgent,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
PatternMatcher ssidPattern =
new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX);
@@ -373,7 +375,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -401,7 +403,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -430,7 +432,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID_1);
+ TEST_UID_1, TEST_PACKAGE_1);
assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -446,7 +448,8 @@
}
private WifiNetworkAgentSpecifier createDefaultNetworkAgentSpecifier() {
- return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration(), TEST_UID);
+ return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration(), TEST_UID,
+ TEST_PACKAGE);
}
}
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
index 2a8df8d..fce247f 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
@@ -38,6 +38,7 @@
@SmallTest
public class WifiNetworkSpecifierTest {
private static final int TEST_UID = 5;
+ private static final String TEST_PACKAGE_NAME = "com.test";
private static final String TEST_SSID = "Test123";
private static final String TEST_BSSID_OUI_BASE_ADDRESS = "12:12:12:00:00:00";
private static final String TEST_BSSID_OUI_MASK = "ff:ff:ff:00:00:00";
@@ -56,7 +57,7 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
Parcel parcelW = Parcel.obtain();
specifier.writeToParcel(parcelW, 0);
@@ -88,7 +89,7 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
assertTrue(specifier.satisfiedBy(null));
assertTrue(specifier.satisfiedBy(new MatchAllNetworkSpecifier()));
@@ -111,14 +112,14 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
WifiNetworkSpecifier specifier2 =
new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
assertTrue(specifier2.satisfiedBy(specifier1));
}
@@ -140,7 +141,7 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration1,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
WifiConfiguration wifiConfiguration2 = new WifiConfiguration();
wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
@@ -149,7 +150,7 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration2,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
assertFalse(specifier2.satisfiedBy(specifier1));
}
@@ -171,14 +172,14 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
WifiNetworkSpecifier specifier2 =
new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
assertFalse(specifier2.satisfiedBy(specifier1));
}
@@ -200,13 +201,42 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
WifiNetworkSpecifier specifier2 =
new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
+
+ assertFalse(specifier2.satisfiedBy(specifier1));
+ }
+
+ /**
+ * Validate NetworkSpecifier matching.
+ * a) Create network specifier 1 for WPA_PSK network
+ * b) Create network specifier 2 with different package name .
+ * c) Ensure that the specifier 2 is not satisfied by specifier 1.
+ */
+ @Test
+ public void testWifiNetworkSpecifierDoesNotSatisfyWhenPackageNameDifferent() {
+ WifiConfiguration wifiConfiguration = new WifiConfiguration();
+ wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+ wifiConfiguration.preSharedKey = TEST_PRESHARED_KEY;
+
+ WifiNetworkSpecifier specifier1 =
+ new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
+ Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
+ MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+ wifiConfiguration,
+ TEST_UID, TEST_PACKAGE_NAME);
+
+ WifiNetworkSpecifier specifier2 =
+ new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
+ Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
+ MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+ wifiConfiguration,
+ TEST_UID, TEST_PACKAGE_NAME + "blah");
assertFalse(specifier2.satisfiedBy(specifier1));
}
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 31f501f..5f76055 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -29,6 +29,10 @@
*/
@SmallTest
public class WifiNetworkSuggestionTest {
+ private static final int TEST_UID = 45677;
+ private static final int TEST_UID_OTHER = 45673;
+ private static final String TEST_PACKAGE_NAME = "com.test.packagename";
+ private static final String TEST_PACKAGE_NAME_OTHER = "com.test.packagenameother";
private static final String TEST_SSID = "\"Test123\"";
private static final String TEST_BSSID = "12:12:12:12:12:12";
private static final String TEST_SSID_1 = "\"Test1234\"";
@@ -43,7 +47,7 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, false, true, 0);
+ new WifiNetworkSuggestion(configuration, false, true, TEST_UID, TEST_PACKAGE_NAME);
Parcel parcelW = Parcel.obtain();
suggestion.writeToParcel(parcelW, 0);
@@ -77,14 +81,16 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, true, false, 0);
+ new WifiNetworkSuggestion(configuration, true, false, TEST_UID,
+ TEST_PACKAGE_NAME);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.BSSID = TEST_BSSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, false, true, 0);
+ new WifiNetworkSuggestion(configuration1, false, true, TEST_UID,
+ TEST_PACKAGE_NAME);
assertEquals(suggestion, suggestion1);
}
@@ -99,13 +105,15 @@
configuration.SSID = TEST_SSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, false, false, 0);
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID_1;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, false, false, 0);
+ new WifiNetworkSuggestion(configuration1, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
assertNotEquals(suggestion, suggestion1);
}
@@ -121,13 +129,15 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, false, false, 0);
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, false, false, 0);
+ new WifiNetworkSuggestion(configuration1, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
assertNotEquals(suggestion, suggestion1);
}
@@ -142,13 +152,15 @@
configuration.SSID = TEST_SSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, false, false, 0);
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, false, false, 0);
+ new WifiNetworkSuggestion(configuration1, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
assertNotEquals(suggestion, suggestion1);
}
@@ -163,10 +175,31 @@
configuration.SSID = TEST_SSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, false, false, 0);
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration, false, false, 1);
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID_OTHER,
+ TEST_PACKAGE_NAME);
+
+ assertNotEquals(suggestion, suggestion1);
+ }
+
+ /**
+ * Check NetworkSuggestion equals returns {@code false} for 2 network suggestions with the same
+ * SSID, BSSID and key mgmt, but different package name.
+ */
+ @Test
+ public void testWifiNetworkSuggestionEqualsFailsWhenPackageNameIsDifferent() {
+ WifiConfiguration configuration = new WifiConfiguration();
+ configuration.SSID = TEST_SSID;
+ configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+ WifiNetworkSuggestion suggestion =
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID, TEST_PACKAGE_NAME);
+
+ WifiNetworkSuggestion suggestion1 =
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+ TEST_PACKAGE_NAME_OTHER);
assertNotEquals(suggestion, suggestion1);
}
diff --git a/wifi/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java b/wifi/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java
new file mode 100644
index 0000000..a947b55
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.net.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.validateMockitoUsage;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+
+/**
+ * Unit tests for {@link android.net.wifi.WifiUsabilityStatsEntry}.
+ */
+@SmallTest
+public class WifiUsabilityStatsEntryTest {
+
+ /**
+ * Setup before tests.
+ */
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Clean up after tests.
+ */
+ @After
+ public void cleanup() {
+ validateMockitoUsage();
+ }
+
+ /**
+ * Verify parcel read/write for Wifi usability stats result.
+ */
+ @Test
+ public void verifyStatsResultWriteAndThenRead() throws Exception {
+ WifiUsabilityStatsEntry writeResult = createResult();
+ WifiUsabilityStatsEntry readResult = parcelWriteRead(writeResult);
+ assertWifiUsabilityStatsEntryEquals(writeResult, readResult);
+ }
+
+ /**
+ * Write the provided {@link WifiUsabilityStatsEntry} to a parcel and deserialize it.
+ */
+ private static WifiUsabilityStatsEntry parcelWriteRead(
+ WifiUsabilityStatsEntry writeResult) throws Exception {
+ Parcel parcel = Parcel.obtain();
+ writeResult.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ return WifiUsabilityStatsEntry.CREATOR.createFromParcel(parcel);
+ }
+
+ private static WifiUsabilityStatsEntry createResult() {
+ return new WifiUsabilityStatsEntry(
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
+ );
+ }
+
+ private static void assertWifiUsabilityStatsEntryEquals(
+ WifiUsabilityStatsEntry expected,
+ WifiUsabilityStatsEntry actual) {
+ assertEquals(expected.timeStampMs, actual.timeStampMs);
+ assertEquals(expected.rssi, actual.rssi);
+ assertEquals(expected.linkSpeedMbps, actual.linkSpeedMbps);
+ assertEquals(expected.totalTxSuccess, actual.totalTxSuccess);
+ assertEquals(expected.totalTxRetries, actual.totalTxRetries);
+ assertEquals(expected.totalTxBad, actual.totalTxBad);
+ assertEquals(expected.totalRxSuccess, actual.totalRxSuccess);
+ assertEquals(expected.totalRadioOnTimeMs, actual.totalRadioOnTimeMs);
+ assertEquals(expected.totalRadioTxTimeMs, actual.totalRadioTxTimeMs);
+ assertEquals(expected.totalRadioRxTimeMs, actual.totalRadioRxTimeMs);
+ assertEquals(expected.totalScanTimeMs, actual.totalScanTimeMs);
+ assertEquals(expected.totalNanScanTimeMs, actual.totalNanScanTimeMs);
+ assertEquals(expected.totalBackgroundScanTimeMs, actual.totalBackgroundScanTimeMs);
+ assertEquals(expected.totalRoamScanTimeMs, actual.totalRoamScanTimeMs);
+ assertEquals(expected.totalPnoScanTimeMs, actual.totalPnoScanTimeMs);
+ assertEquals(expected.totalHotspot2ScanTimeMs, actual.totalHotspot2ScanTimeMs);
+ assertEquals(expected.totalCcaBusyFreqTimeMs, actual.totalCcaBusyFreqTimeMs);
+ assertEquals(expected.totalRadioOnFreqTimeMs, actual.totalRadioOnFreqTimeMs);
+ assertEquals(expected.totalBeaconRx, actual.totalBeaconRx);
+ }
+}