Merge "Make night display auto mode system API"
diff --git a/Android.bp b/Android.bp
index 1d93ee7..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",
@@ -471,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",
@@ -504,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",
@@ -523,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",
@@ -637,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",
@@ -691,6 +686,7 @@
"location/java",
"lowpan/java",
"media/java",
+ "media/apex/java",
"media/mca/effect/java",
"media/mca/filterfw/java",
"media/mca/filterpacks/java",
@@ -723,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,
@@ -751,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",
@@ -808,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
@@ -1271,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",
],
@@ -1333,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,
@@ -1779,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",
@@ -1787,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 475f8e1..8b83803 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5793,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);
@@ -6578,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);
@@ -6600,6 +6600,7 @@
method @Nullable public String[] getAccountTypesWithManagementDisabled();
method @Nullable public java.util.List<android.content.ComponentName> getActiveAdmins();
method @NonNull public java.util.Set<java.lang.String> getAffiliationIds(@NonNull android.content.ComponentName);
+ method public java.util.List<java.lang.String> getAlwaysOnVpnLockdownWhitelist(@NonNull android.content.ComponentName);
method @Nullable public String getAlwaysOnVpnPackage(@NonNull android.content.ComponentName);
method @WorkerThread @NonNull public android.os.Bundle getApplicationRestrictions(@Nullable android.content.ComponentName, String);
method @Deprecated @Nullable public String getApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName);
@@ -6608,7 +6609,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);
@@ -6674,6 +6675,7 @@
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(@NonNull android.content.ComponentName);
method public boolean isAffiliatedUser();
+ method public boolean isAlwaysOnVpnLockdownEnabled(@NonNull android.content.ComponentName);
method public boolean isApplicationHidden(@NonNull android.content.ComponentName, String);
method public boolean isBackupServiceEnabled(@NonNull android.content.ComponentName);
method @Deprecated public boolean isCallerApplicationRestrictionsManagingPackage();
@@ -6698,7 +6700,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);
@@ -6712,6 +6713,7 @@
method public void setAccountManagementDisabled(@NonNull android.content.ComponentName, String, boolean);
method public void setAffiliationIds(@NonNull android.content.ComponentName, @NonNull java.util.Set<java.lang.String>);
method public void setAlwaysOnVpnPackage(@NonNull android.content.ComponentName, @Nullable String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
+ method public void setAlwaysOnVpnPackage(@NonNull android.content.ComponentName, @Nullable String, boolean, @Nullable java.util.List<java.lang.String>) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
method public boolean setApplicationHidden(@NonNull android.content.ComponentName, String, boolean);
method @WorkerThread public void setApplicationRestrictions(@Nullable android.content.ComponentName, String, android.os.Bundle);
method @Deprecated public void setApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName, @Nullable String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -6720,6 +6722,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>);
@@ -6787,7 +6790,7 @@
method public void uninstallCaCert(@Nullable android.content.ComponentName, byte[]);
method public boolean updateOverrideApn(@NonNull android.content.ComponentName, int, @NonNull android.telephony.data.ApnSetting);
method public void wipeData(int);
- method public void wipeData(int, CharSequence);
+ method public void wipeData(int, @NonNull CharSequence);
field public static final String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE";
field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
@@ -6931,6 +6934,7 @@
field public static final int WIPE_EUICC = 4; // 0x4
field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1
field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
+ field public static final int WIPE_SILENTLY = 8; // 0x8
}
public abstract static class DevicePolicyManager.InstallUpdateCallback {
@@ -8610,6 +8614,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);
@@ -8705,6 +8716,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
@@ -10213,6 +10225,7 @@
field public static final String ACTION_MEDIA_REMOVED = "android.intent.action.MEDIA_REMOVED";
field public static final String ACTION_MEDIA_SCANNER_FINISHED = "android.intent.action.MEDIA_SCANNER_FINISHED";
field public static final String ACTION_MEDIA_SCANNER_SCAN_FILE = "android.intent.action.MEDIA_SCANNER_SCAN_FILE";
+ field public static final String ACTION_MEDIA_SCANNER_SCAN_VOLUME = "android.intent.action.MEDIA_SCANNER_SCAN_VOLUME";
field public static final String ACTION_MEDIA_SCANNER_STARTED = "android.intent.action.MEDIA_SCANNER_STARTED";
field public static final String ACTION_MEDIA_SHARED = "android.intent.action.MEDIA_SHARED";
field public static final String ACTION_MEDIA_UNMOUNTABLE = "android.intent.action.MEDIA_UNMOUNTABLE";
@@ -11210,6 +11223,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 +11251,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 +11455,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();
@@ -11675,6 +11698,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";
@@ -17078,6 +17102,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;
@@ -25409,6 +25434,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();
@@ -28470,6 +28496,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();
@@ -28974,6 +29001,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();
@@ -29184,6 +29234,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[]);
@@ -35250,11 +35301,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 {
@@ -38195,6 +38251,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 {
@@ -38401,28 +38459,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 {
@@ -38474,24 +38532,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 {
@@ -39072,6 +39130,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";
@@ -41333,6 +41392,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);
@@ -41379,6 +41454,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();
@@ -41459,6 +41552,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();
@@ -41477,6 +41572,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);
@@ -41671,6 +41797,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);
@@ -41680,9 +41807,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 {
@@ -43043,6 +43176,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";
}
@@ -43648,6 +43790,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);
@@ -43895,7 +44038,9 @@
field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool";
field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string";
field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
+ field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
+ field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool";
field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool";
field public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
field public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
@@ -44659,6 +44804,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);
@@ -44709,6 +44855,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 {
@@ -52621,6 +52769,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);
}
@@ -52637,12 +52786,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 {
@@ -52981,8 +53130,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);
@@ -53079,6 +53228,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);
}
@@ -53099,7 +53258,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);
@@ -53336,6 +53495,7 @@
public final class TextClassificationManager {
method @NonNull public android.view.textclassifier.TextClassifier createTextClassificationSession(@NonNull android.view.textclassifier.TextClassificationContext);
+ method @NonNull public android.view.textclassifier.TextClassifier getLocalTextClassifier();
method @NonNull public android.view.textclassifier.TextClassifier getTextClassifier();
method public void setTextClassificationSessionFactory(@Nullable android.view.textclassifier.TextClassificationSessionFactory);
method public void setTextClassifier(@Nullable android.view.textclassifier.TextClassifier);
@@ -56847,7 +57007,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/system-current.txt b/api/system-current.txt
index cca01d2..fe2b4d9 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
}
@@ -1052,6 +1053,7 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void removeRoleHolderAsUser(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.role.RoleManagerCallback);
method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String);
method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public void setRoleNamesFromController(@NonNull java.util.List<java.lang.String>);
+ field public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT";
}
public interface RoleManagerCallback {
@@ -1112,6 +1114,7 @@
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);
@@ -1119,6 +1122,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);
@@ -1147,7 +1151,7 @@
field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE";
}
- public abstract class BluetoothAdapter.MetadataListener {
+ public abstract static class BluetoothAdapter.MetadataListener {
ctor public BluetoothAdapter.MetadataListener();
method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String);
}
@@ -1155,14 +1159,18 @@
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_PRIVILEGED) public boolean getSilenceMode();
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);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setSilenceMode(boolean);
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 String ACTION_SILENCE_MODE_CHANGED = "android.bluetooth.device.action.SILENCE_MODE_CHANGED";
+ field public static final String EXTRA_SILENCE_ENABLED = "android.bluetooth.device.extra.SILENCE_ENABLED";
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
@@ -3401,6 +3409,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 {
@@ -3576,6 +3593,10 @@
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();
@@ -4015,6 +4036,7 @@
}
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();
@@ -4035,6 +4057,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;
@@ -4067,6 +4093,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();
@@ -4077,9 +4104,12 @@
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();
@@ -4097,6 +4127,8 @@
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);
@@ -4151,6 +4183,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
@@ -4284,6 +4317,7 @@
}
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);
@@ -4332,6 +4366,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
@@ -4603,6 +4651,7 @@
}
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);
@@ -4618,6 +4667,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);
@@ -4627,6 +4677,7 @@
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startSubscriptionProvisioning(android.net.wifi.hotspot2.OsuProvider, android.net.wifi.hotspot2.ProvisioningCallback, @Nullable android.os.Handler);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession();
method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void unregisterNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback);
+ method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void updateWifiUsabilityScore(int, int, int);
field public static final int CHANGE_REASON_ADDED = 0; // 0x0
field public static final int CHANGE_REASON_CONFIG_CHANGE = 2; // 0x2
field public static final int CHANGE_REASON_REMOVED = 1; // 0x1
@@ -4675,6 +4726,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();
@@ -4804,6 +4859,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 {
@@ -5559,6 +5639,30 @@
field public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant";
}
+ public static interface DeviceConfig.ActivityManager {
+ field public static final String KEY_COMPACT_ACTION_1 = "compact_action_1";
+ field public static final String KEY_COMPACT_ACTION_2 = "compact_action_2";
+ field public static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
+ field public static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
+ field public static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
+ field public static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
+ field public static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
+ field public static final String KEY_USE_COMPACTION = "use_compaction";
+ field public static final String NAMESPACE = "activity_manager";
+ }
+
+ 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);
}
@@ -5750,6 +5854,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
@@ -6025,10 +6130,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 {
@@ -6042,7 +6144,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 {
@@ -6268,77 +6369,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);
@@ -7579,10 +7609,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
@@ -8655,12 +8688,20 @@
public class ProvisioningManager {
method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(android.content.Context, int);
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int);
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int);
+ method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
+ method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setProvisioningIntValue(int, int);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setProvisioningStringValue(int, String);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, String);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+ field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
+ field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
+ field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
+ field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
+ field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
+ field public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY";
}
public static class ProvisioningManager.Callback {
@@ -9087,9 +9128,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();
diff --git a/api/test-current.txt b/api/test-current.txt
index 1f894c2..8e638fd 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -14,6 +14,10 @@
field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
}
+ public static final class R.array {
+ field public static final int config_defaultRoleHolders = 17235974; // 0x1070006
+ }
+
}
package android.animation {
@@ -332,6 +336,7 @@
method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHolders(@NonNull String);
method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void removeRoleHolderAsUser(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.role.RoleManagerCallback);
+ field public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT";
}
public interface RoleManagerCallback {
@@ -802,11 +807,16 @@
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();
@@ -814,7 +824,10 @@
}
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();
@@ -826,6 +839,8 @@
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);
@@ -843,6 +858,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
@@ -929,6 +945,7 @@
}
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);
@@ -977,6 +994,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
@@ -1273,15 +1304,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;
}
@@ -1688,10 +1715,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 {
@@ -1705,7 +1729,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 {
@@ -1729,84 +1752,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);
@@ -2341,6 +2294,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/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 3defdc5..062ba65 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -102,7 +102,17 @@
String op = nextArg();
Slog.v(TAG, "Running " + op + " for user:" + userId);
- if (!isBmgrActive(userId)) {
+ if (mBmgr == null) {
+ System.err.println(BMGR_NOT_RUNNING_ERR);
+ return;
+ }
+
+ if ("activate".equals(op)) {
+ doActivateService(userId);
+ return;
+ }
+
+ if (!isBackupActive(userId)) {
return;
}
@@ -175,12 +185,7 @@
showUsage();
}
- boolean isBmgrActive(@UserIdInt int userId) {
- if (mBmgr == null) {
- System.err.println(BMGR_NOT_RUNNING_ERR);
- return false;
- }
-
+ boolean isBackupActive(@UserIdInt int userId) {
try {
if (!mBmgr.isBackupServiceActive(userId)) {
System.err.println(BMGR_NOT_RUNNING_ERR);
@@ -845,6 +850,27 @@
}
}
+ private void doActivateService(int userId) {
+ String arg = nextArg();
+ if (arg == null) {
+ showUsage();
+ return;
+ }
+
+ try {
+ boolean activate = Boolean.parseBoolean(arg);
+ mBmgr.setBackupServiceActive(userId, activate);
+ System.out.println(
+ "Backup service now "
+ + (activate ? "activated" : "deactivated")
+ + " for user "
+ + userId);
+ } catch (RemoteException e) {
+ System.err.println(e.toString());
+ System.err.println(BMGR_NOT_RUNNING_ERR);
+ }
+ }
+
private String nextArg() {
if (mNextArg >= mArgs.length) {
return null;
@@ -880,6 +906,7 @@
System.err.println(" bmgr backupnow [--monitor|--monitor-verbose] --all|PACKAGE...");
System.err.println(" bmgr cancel backups");
System.err.println(" bmgr init TRANSPORT...");
+ System.err.println(" bmgr activate BOOL");
System.err.println("");
System.err.println("The '--user' option specifies the user on which the operation is run.");
System.err.println("It must be the first argument before the operation.");
@@ -946,6 +973,11 @@
System.err.println("");
System.err.println("The 'init' command initializes the given transports, wiping all data");
System.err.println("from their backing data stores.");
+ System.err.println("");
+ System.err.println("The 'activate' command activates or deactivates the backup service.");
+ System.err.println("If the argument is 'true' it will be activated, otherwise it will be");
+ System.err.println("deactivated. When deactivated, the service will not be running and no");
+ System.err.println("operations can be performed until activation.");
}
private static class BackupMonitor extends IBackupManagerMonitor.Stub {
diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp
index c455ac0..0c581f3 100644
--- a/cmds/idmap2/idmap2/Create.cpp
+++ b/cmds/idmap2/idmap2/Create.cpp
@@ -39,6 +39,7 @@
using android::idmap2::PolicyFlags;
using android::idmap2::Result;
using android::idmap2::utils::kIdmapFilePermissionMask;
+using android::idmap2::utils::UidHasWriteAccessToPath;
bool Create(const std::vector<std::string>& args, std::ostream& out_error) {
std::string target_apk_path;
@@ -66,6 +67,13 @@
return false;
}
+ const uid_t uid = getuid();
+ if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+ out_error << "error: uid " << uid << " does not have write access to " << idmap_path
+ << std::endl;
+ return false;
+ }
+
PolicyBitmask fulfilled_policies = 0;
if (auto result = PoliciesToBitmask(policies, out_error)) {
fulfilled_policies |= *result;
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index a3c7527..f30ce9b 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -27,6 +27,7 @@
#include "android-base/macros.h"
#include "android-base/stringprintf.h"
+#include "binder/IPCThreadState.h"
#include "utils/String8.h"
#include "utils/Trace.h"
@@ -38,18 +39,19 @@
#include "idmap2d/Idmap2Service.h"
+using android::IPCThreadState;
using android::binder::Status;
using android::idmap2::BinaryStreamVisitor;
using android::idmap2::Idmap;
using android::idmap2::IdmapHeader;
using android::idmap2::PolicyBitmask;
using android::idmap2::Result;
+using android::idmap2::utils::kIdmapCacheDir;
using android::idmap2::utils::kIdmapFilePermissionMask;
+using android::idmap2::utils::UidHasWriteAccessToPath;
namespace {
-constexpr const char* kIdmapCacheDir = "/data/resource-cache";
-
Status ok() {
return Status::ok();
}
@@ -77,7 +79,13 @@
Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path,
int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) {
assert(_aidl_return);
+ const uid_t uid = IPCThreadState::self()->getCallingUid();
const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+ if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+ *_aidl_return = false;
+ return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
+ idmap_path.c_str(), uid));
+ }
if (unlink(idmap_path.c_str()) != 0) {
*_aidl_return = false;
return error("failed to unlink " + idmap_path + ": " + strerror(errno));
@@ -118,6 +126,13 @@
const PolicyBitmask policy_bitmask = ConvertAidlArgToPolicyBitmask(fulfilled_policies);
+ const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+ const uid_t uid = IPCThreadState::self()->getCallingUid();
+ if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+ return error(base::StringPrintf("will not write to %s: calling uid %d lacks write accesss",
+ idmap_path.c_str(), uid));
+ }
+
const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
if (!target_apk) {
return error("failed to load apk " + target_apk_path);
@@ -137,7 +152,6 @@
}
umask(kIdmapFilePermissionMask);
- const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
std::ofstream fout(idmap_path);
if (fout.fail()) {
return error("failed to open idmap path " + idmap_path);
diff --git a/cmds/idmap2/include/idmap2/FileUtils.h b/cmds/idmap2/include/idmap2/FileUtils.h
index 5c41c49..3f03236 100644
--- a/cmds/idmap2/include/idmap2/FileUtils.h
+++ b/cmds/idmap2/include/idmap2/FileUtils.h
@@ -17,6 +17,8 @@
#ifndef IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
#define IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
+#include <sys/types.h>
+
#include <functional>
#include <memory>
#include <string>
@@ -24,6 +26,7 @@
namespace android::idmap2::utils {
+constexpr const char* kIdmapCacheDir = "/data/resource-cache";
constexpr const mode_t kIdmapFilePermissionMask = 0133; // u=rw,g=r,o=r
typedef std::function<bool(unsigned char type /* DT_* from dirent.h */, const std::string& path)>
@@ -35,6 +38,8 @@
std::unique_ptr<std::string> ReadFile(const std::string& path);
+bool UidHasWriteAccessToPath(uid_t uid, const std::string& path);
+
} // namespace android::idmap2::utils
#endif // IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
diff --git a/cmds/idmap2/libidmap2/FileUtils.cpp b/cmds/idmap2/libidmap2/FileUtils.cpp
index 0255727..a9b68cd 100644
--- a/cmds/idmap2/libidmap2/FileUtils.cpp
+++ b/cmds/idmap2/libidmap2/FileUtils.cpp
@@ -19,12 +19,20 @@
#include <unistd.h>
#include <cerrno>
+#include <climits>
+#include <cstdlib>
+#include <cstring>
#include <fstream>
#include <memory>
#include <string>
#include <utility>
#include <vector>
+#include "android-base/file.h"
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "private/android_filesystem_config.h"
+
#include "idmap2/FileUtils.h"
namespace android::idmap2::utils {
@@ -77,4 +85,26 @@
return r == 0 ? std::move(str) : nullptr;
}
+#ifdef __ANDROID__
+bool UidHasWriteAccessToPath(uid_t uid, const std::string& path) {
+ // resolve symlinks and relative paths; the directories must exist
+ std::string canonical_path;
+ if (!base::Realpath(base::Dirname(path), &canonical_path)) {
+ return false;
+ }
+
+ const std::string cache_subdir = base::StringPrintf("%s/", kIdmapCacheDir);
+ if (canonical_path == kIdmapCacheDir ||
+ canonical_path.compare(0, cache_subdir.size(), cache_subdir) == 0) {
+ // limit access to /data/resource-cache to root and system
+ return uid == AID_ROOT || uid == AID_SYSTEM;
+ }
+ return true;
+}
+#else
+bool UidHasWriteAccessToPath(uid_t uid ATTRIBUTE_UNUSED, const std::string& path ATTRIBUTE_UNUSED) {
+ return true;
+}
+#endif
+
} // namespace android::idmap2::utils
diff --git a/cmds/idmap2/tests/FileUtilsTests.cpp b/cmds/idmap2/tests/FileUtilsTests.cpp
index d9d9a7f..45f84fe 100644
--- a/cmds/idmap2/tests/FileUtilsTests.cpp
+++ b/cmds/idmap2/tests/FileUtilsTests.cpp
@@ -22,6 +22,8 @@
#include "gtest/gtest.h"
#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "private/android_filesystem_config.h"
#include "idmap2/FileUtils.h"
@@ -71,4 +73,25 @@
close(pipefd[0]);
}
+#ifdef __ANDROID__
+TEST(FileUtilsTests, UidHasWriteAccessToPath) {
+ constexpr const char* tmp_path = "/data/local/tmp/test@idmap";
+ const std::string cache_path(base::StringPrintf("%s/test@idmap", kIdmapCacheDir));
+ const std::string sneaky_cache_path(base::StringPrintf("/data/../%s/test@idmap", kIdmapCacheDir));
+
+ ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, tmp_path));
+ ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, cache_path));
+ ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, sneaky_cache_path));
+
+ ASSERT_TRUE(UidHasWriteAccessToPath(AID_SYSTEM, tmp_path));
+ ASSERT_TRUE(UidHasWriteAccessToPath(AID_SYSTEM, cache_path));
+ ASSERT_TRUE(UidHasWriteAccessToPath(AID_SYSTEM, sneaky_cache_path));
+
+ constexpr const uid_t AID_SOME_APP = AID_SYSTEM + 1;
+ ASSERT_TRUE(UidHasWriteAccessToPath(AID_SOME_APP, tmp_path));
+ ASSERT_FALSE(UidHasWriteAccessToPath(AID_SOME_APP, cache_path));
+ ASSERT_FALSE(UidHasWriteAccessToPath(AID_SOME_APP, sneaky_cache_path));
+}
+#endif
+
} // namespace android::idmap2::utils
diff --git a/cmds/idmap2/tests/Idmap2BinaryTests.cpp b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
index 4334fa6..c550eaf 100644
--- a/cmds/idmap2/tests/Idmap2BinaryTests.cpp
+++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
@@ -38,6 +38,7 @@
#include "gtest/gtest.h"
#include "androidfw/PosixUtils.h"
+#include "private/android_filesystem_config.h"
#include "idmap2/FileUtils.h"
#include "idmap2/Idmap.h"
@@ -69,9 +70,23 @@
ASSERT_NO_FATAL_FAILURE(AssertIdmap(idmap_ref, target_apk_path, overlay_apk_path)); \
} while (0)
+#ifdef __ANDROID__
+#define SKIP_TEST_IF_CANT_EXEC_IDMAP2 \
+ do { \
+ const uid_t uid = getuid(); \
+ if (uid != AID_ROOT && uid != AID_SYSTEM) { \
+ GTEST_SKIP(); \
+ } \
+ } while (0)
+#else
+#define SKIP_TEST_IF_CANT_EXEC_IDMAP2
+#endif
+
} // namespace
TEST_F(Idmap2BinaryTests, Create) {
+ SKIP_TEST_IF_CANT_EXEC_IDMAP2;
+
// clang-format off
auto result = ExecuteBinary({"idmap2",
"create",
@@ -97,6 +112,8 @@
}
TEST_F(Idmap2BinaryTests, Dump) {
+ SKIP_TEST_IF_CANT_EXEC_IDMAP2;
+
// clang-format off
auto result = ExecuteBinary({"idmap2",
"create",
@@ -144,6 +161,8 @@
}
TEST_F(Idmap2BinaryTests, Scan) {
+ SKIP_TEST_IF_CANT_EXEC_IDMAP2;
+
const std::string overlay_static_1_apk_path = GetTestDataPath() + "/overlay/overlay-static-1.apk";
const std::string overlay_static_2_apk_path = GetTestDataPath() + "/overlay/overlay-static-2.apk";
const std::string idmap_static_1_path =
@@ -236,6 +255,8 @@
}
TEST_F(Idmap2BinaryTests, Lookup) {
+ SKIP_TEST_IF_CANT_EXEC_IDMAP2;
+
// clang-format off
auto result = ExecuteBinary({"idmap2",
"create",
@@ -285,6 +306,8 @@
}
TEST_F(Idmap2BinaryTests, InvalidCommandLineOptions) {
+ SKIP_TEST_IF_CANT_EXEC_IDMAP2;
+
const std::string invalid_target_apk_path = GetTestDataPath() + "/DOES-NOT-EXIST";
// missing mandatory options
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 60b2e25..8fb01b4 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -34,6 +34,7 @@
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";
@@ -206,6 +207,8 @@
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.
@@ -3018,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;
@@ -4521,3 +4524,28 @@
// 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/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 19a7389..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"
@@ -150,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/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index fc47f67..ee2fe8f 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -3518,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/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 1063be4..92f47e7 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -8218,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/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a3243a5..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() {
@@ -5960,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();
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 75c9054..181acce 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -101,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
@@ -123,14 +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));
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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 55a3acb..8ca3544 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3291,7 +3291,7 @@
*/
public int getPasswordMaximumLength(int quality) {
PackageManager pm = mContext.getPackageManager();
- if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ID_ATTESTATION)) {
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)) {
return 0;
}
// Kind-of arbitrary.
@@ -3980,6 +3980,10 @@
*/
public static final int WIPE_EUICC = 0x0004;
+ /**
+ * Flag for {@link #wipeData(int)}: won't show reason for wiping to the user.
+ */
+ public static final int WIPE_SILENTLY = 0x0008;
/**
* Ask that all user data be wiped. If called as a secondary user, the user will be removed and
@@ -3991,9 +3995,10 @@
* be able to call this method; if it has not, a security exception will be thrown.
*
* @param flags Bit mask of additional options: currently supported flags are
- * {@link #WIPE_EXTERNAL_STORAGE} and {@link #WIPE_RESET_PROTECTION_DATA}.
+ * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
+ * {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
* @throws SecurityException if the calling application does not own an active administrator
- * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
+ * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
*/
public void wipeData(int flags) {
throwIfParentInstance("wipeData");
@@ -4013,16 +4018,21 @@
* be able to call this method; if it has not, a security exception will be thrown.
*
* @param flags Bit mask of additional options: currently supported flags are
- * {@link #WIPE_EXTERNAL_STORAGE} and {@link #WIPE_RESET_PROTECTION_DATA}.
+ * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA} and
+ * {@link #WIPE_EUICC}.
* @param reason a string that contains the reason for wiping data, which can be
- * presented to the user. If the string is null or empty, user won't be notified.
+ * presented to the user.
* @throws SecurityException if the calling application does not own an active administrator
- * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
- * @throws IllegalArgumentException if the input reason string is null or empty.
+ * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
+ * @throws IllegalArgumentException if the input reason string is null or empty, or if
+ * {@link #WIPE_SILENTLY} is set.
*/
- public void wipeData(int flags, CharSequence reason) {
+ public void wipeData(int flags, @NonNull CharSequence reason) {
throwIfParentInstance("wipeData");
- wipeDataInternal(flags, reason != null ? reason.toString() : null);
+ Preconditions.checkNotNull(reason, "reason string is null");
+ Preconditions.checkStringNotEmpty(reason, "reason string is empty");
+ Preconditions.checkArgument((flags & WIPE_SILENTLY) == 0, "WIPE_SILENTLY cannot be set");
+ wipeDataInternal(flags, reason.toString());
}
/**
@@ -4033,7 +4043,7 @@
* @see #wipeData(int, CharSequence)
* @hide
*/
- private void wipeDataInternal(int flags, String wipeReasonForUser) {
+ private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
if (mService != null) {
try {
mService.wipeDataWithReason(flags, wipeReasonForUser);
@@ -5049,11 +5059,16 @@
}
/**
+ * Service-specific error code used in implementation of {@code setAlwaysOnVpnPackage} methods.
+ * @hide
+ */
+ public static final int ERROR_VPN_PACKAGE_NOT_FOUND = 1;
+
+ /**
* Called by a device or profile owner to configure an always-on VPN connection through a
* specific application for the current user. This connection is automatically granted and
* persisted after a reboot.
- * <p>
- * To support the always-on feature, an app must
+ * <p> To support the always-on feature, an app must
* <ul>
* <li>declare a {@link android.net.VpnService} in its manifest, guarded by
* {@link android.Manifest.permission#BIND_VPN_SERVICE};</li>
@@ -5062,12 +5077,13 @@
* {@link android.net.VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}.</li>
* </ul>
* The call will fail if called with the package name of an unsupported VPN app.
+ * <p> Enabling lockdown via {@code lockdownEnabled} argument carries the risk that any failure
+ * of the VPN provider could break networking for all apps.
*
* @param vpnPackage The package name for an installed VPN app on the device, or {@code null} to
* remove an existing always-on VPN configuration.
* @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
- * {@code false} otherwise. This carries the risk that any failure of the VPN provider
- * could break networking for all apps. This has no effect when clearing.
+ * {@code false} otherwise. This has no effect when clearing.
* @throws SecurityException if {@code admin} is not a device or a profile owner.
* @throws NameNotFoundException if {@code vpnPackage} is not installed.
* @throws UnsupportedOperationException if {@code vpnPackage} exists but does not support being
@@ -5076,11 +5092,46 @@
public void setAlwaysOnVpnPackage(@NonNull ComponentName admin, @Nullable String vpnPackage,
boolean lockdownEnabled)
throws NameNotFoundException, UnsupportedOperationException {
+ setAlwaysOnVpnPackage(admin, vpnPackage, lockdownEnabled, Collections.emptyList());
+ }
+
+ /**
+ * A version of {@link #setAlwaysOnVpnPackage(ComponentName, String, boolean)} that allows the
+ * admin to specify a set of apps that should be able to access the network directly when VPN
+ * is not connected. When VPN connects these apps switch over to VPN if allowed to use that VPN.
+ * System apps can always bypass VPN.
+ * <p> Note that the system doesn't update the whitelist when packages are installed or
+ * uninstalled, the admin app must call this method to keep the list up to date.
+ *
+ * @param vpnPackage package name for an installed VPN app on the device, or {@code null}
+ * to remove an existing always-on VPN configuration
+ * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
+ * {@code false} otherwise. This has no effect when clearing.
+ * @param lockdownWhitelist Packages that will be able to access the network directly when VPN
+ * is in lockdown mode but not connected. Has no effect when clearing.
+ * @throws SecurityException if {@code admin} is not a device or a profile
+ * owner.
+ * @throws NameNotFoundException if {@code vpnPackage} or one of
+ * {@code lockdownWhitelist} is not installed.
+ * @throws UnsupportedOperationException if {@code vpnPackage} exists but does
+ * not support being set as always-on, or if always-on VPN is not
+ * available.
+ */
+ public void setAlwaysOnVpnPackage(@NonNull ComponentName admin, @Nullable String vpnPackage,
+ boolean lockdownEnabled, @Nullable List<String> lockdownWhitelist)
+ throws NameNotFoundException, UnsupportedOperationException {
throwIfParentInstance("setAlwaysOnVpnPackage");
if (mService != null) {
try {
- if (!mService.setAlwaysOnVpnPackage(admin, vpnPackage, lockdownEnabled)) {
- throw new NameNotFoundException(vpnPackage);
+ mService.setAlwaysOnVpnPackage(
+ admin, vpnPackage, lockdownEnabled, lockdownWhitelist);
+ } catch (ServiceSpecificException e) {
+ switch (e.errorCode) {
+ case ERROR_VPN_PACKAGE_NOT_FOUND:
+ throw new NameNotFoundException(e.getMessage());
+ default:
+ throw new RuntimeException(
+ "Unknown error setting always-on VPN: " + e.errorCode);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -5089,6 +5140,51 @@
}
/**
+ * Called by device or profile owner to query whether current always-on VPN is configured in
+ * lockdown mode. Returns {@code false} when no always-on configuration is set.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ *
+ * @throws SecurityException if {@code admin} is not a device or a profile owner.
+ *
+ * @see #setAlwaysOnVpnPackage(ComponentName, String, boolean)
+ */
+ public boolean isAlwaysOnVpnLockdownEnabled(@NonNull ComponentName admin) {
+ throwIfParentInstance("isAlwaysOnVpnLockdownEnabled");
+ if (mService != null) {
+ try {
+ return mService.isAlwaysOnVpnLockdownEnabled(admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called by device or profile owner to query the list of packages that are allowed to access
+ * the network directly when always-on VPN is in lockdown mode but not connected. Returns
+ * {@code null} when always-on VPN is not active or not in lockdown mode.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ *
+ * @throws SecurityException if {@code admin} is not a device or a profile owner.
+ *
+ * @see #setAlwaysOnVpnPackage(ComponentName, String, boolean, List)
+ */
+ public List<String> getAlwaysOnVpnLockdownWhitelist(@NonNull ComponentName admin) {
+ throwIfParentInstance("getAlwaysOnVpnLockdownWhitelist");
+ if (mService != null) {
+ try {
+ return mService.getAlwaysOnVpnLockdownWhitelist(admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Called by a device or profile owner to read the name of the package administering an
* always-on VPN connection for the current user. If there is no such package, or the always-on
* VPN is provided by the system instead of by an application, {@code null} will be returned.
@@ -10428,23 +10524,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();
}
@@ -10452,52 +10553,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();
}
@@ -10506,22 +10579,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 {
@@ -10535,27 +10607,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..5790fda 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -187,8 +187,10 @@
void setCertInstallerPackage(in ComponentName who, String installerPackage);
String getCertInstallerPackage(in ComponentName who);
- boolean setAlwaysOnVpnPackage(in ComponentName who, String vpnPackage, boolean lockdown);
+ boolean setAlwaysOnVpnPackage(in ComponentName who, String vpnPackage, boolean lockdown, in List<String> lockdownWhitelist);
String getAlwaysOnVpnPackage(in ComponentName who);
+ boolean isAlwaysOnVpnLockdownEnabled(in ComponentName who);
+ List<String> getAlwaysOnVpnLockdownWhitelist(in ComponentName who);
void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity);
void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName);
@@ -424,8 +426,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/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/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index a6abe0b..ddd5313 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -172,6 +172,15 @@
public static final String ROLE_CALL_COMPANION_APP = "android.app.role.CALL_COMPANION_APP";
/**
+ * The name of the assistant app role.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT";
+
+ /**
* The action used to request user approval of a role for an application.
*
* @hide
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index d2934b9..b1500c1 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -55,6 +55,9 @@
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);
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index d2de887..51397a2 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -619,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.
@@ -682,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.
@@ -736,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
@@ -743,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.
@@ -766,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.
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index d2d0cf9..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;
@@ -270,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 97bc079..ab8c196 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1900,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
@@ -2051,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) {
@@ -2468,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,
@@ -2525,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;
}
@@ -3253,7 +3276,7 @@
* @hide
*/
@SystemApi
- public abstract class MetadataListener {
+ public abstract static class MetadataListener {
/**
* Callback triggered if the metadata of {@link BluetoothDevice} registered in
* {@link #registerMetadataListener}.
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 17cf702..4d8dc35 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -532,6 +532,28 @@
"android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL";
/**
+ * Intent to broadcast silence mode changed.
+ * Alway contains the extra field {@link #EXTRA_DEVICE}
+ * Alway contains the extra field {@link #EXTRA_SILENCE_ENABLED}
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_SILENCE_MODE_CHANGED =
+ "android.bluetooth.device.action.SILENCE_MODE_CHANGED";
+
+ /**
+ * Used as an extra field in {@link #ACTION_SILENCE_MODE_CHANGED} intent,
+ * contains whether device is in silence mode as boolean.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_SILENCE_ENABLED =
+ "android.bluetooth.device.extra.SILENCE_ENABLED";
+
+ /**
* Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intent.
*
* @hide
@@ -1592,6 +1614,70 @@
}
/**
+ * Set the Bluetooth device silence mode.
+ *
+ * When the {@link BluetoothDevice} enters silence mode, and the {@link BluetoothDevice}
+ * is an active device (for A2DP or HFP), the active device for that profile
+ * will be set to null.
+ * If the {@link BluetoothDevice} exits silence mode while the A2DP or HFP
+ * active device is null, the {@link BluetoothDevice} will be set as the
+ * active device for that profile.
+ * If the {@link BluetoothDevice} is disconnected, it exits silence mode.
+ * If the {@link BluetoothDevice} is set as the active device for A2DP or
+ * HFP, while silence mode is enabled, then the device will exit silence mode.
+ * If the {@link BluetoothDevice} is in silence mode, AVRCP position change
+ * event and HFP AG indicators will be disabled.
+ * If the {@link BluetoothDevice} is not connected with A2DP or HFP, it cannot
+ * enter silence mode.
+ *
+ * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
+ *
+ * @param silence true to enter silence mode, false to exit
+ * @return true on success, false on error.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean setSilenceMode(boolean silence) {
+ final IBluetooth service = sService;
+ if (service == null) {
+ return false;
+ }
+ try {
+ if (getSilenceMode() == silence) {
+ return true;
+ }
+ return service.setSilenceMode(this, silence);
+ } catch (RemoteException e) {
+ Log.e(TAG, "setSilenceMode fail", e);
+ return false;
+ }
+ }
+
+ /**
+ * Get the device silence mode status
+ *
+ * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
+ *
+ * @return true on device in silence mode, otherwise false.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean getSilenceMode() {
+ final IBluetooth service = sService;
+ if (service == null) {
+ return false;
+ }
+ try {
+ return service.getSilenceMode(this);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getSilenceMode fail", e);
+ return false;
+ }
+ }
+
+ /**
* Sets whether the phonebook access is allowed to this device.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
*
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/Intent.java b/core/java/android/content/Intent.java
index b46203c..edd765b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3047,6 +3047,13 @@
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_MEDIA_SCANNER_SCAN_FILE = "android.intent.action.MEDIA_SCANNER_SCAN_FILE";
+ /**
+ * Broadcast Action: Request the media scanner to scan a storage volume and add it to the media database.
+ * The path to the storage volume is contained in the Intent.mData field.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MEDIA_SCANNER_SCAN_VOLUME = "android.intent.action.MEDIA_SCANNER_SCAN_VOLUME";
+
/**
* Broadcast Action: The "Media Button" was pressed. Includes a single
* extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
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/core/java/android/content/pm/LauncherApps.aidl b/core/java/android/content/pm/LauncherApps.aidl
new file mode 100644
index 0000000..1d98ad1
--- /dev/null
+++ b/core/java/android/content/pm/LauncherApps.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+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 9e2f316..783ee64 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2223,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.
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/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/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/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index c809cca..243b0eb 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
@@ -1007,14 +1014,20 @@
* to remove an existing always-on VPN configuration.
* @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
* {@code false} otherwise.
+ * @param lockdownWhitelist The list of packages that are allowed to access network directly
+ * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so
+ * this method must be called when a package that should be whitelisted is installed or
+ * uninstalled.
* @return {@code true} if the package is set as always-on VPN controller;
* {@code false} otherwise.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage,
- boolean lockdownEnabled) {
+ boolean lockdownEnabled, @Nullable List<String> lockdownWhitelist) {
try {
- return mService.setAlwaysOnVpnPackage(userId, vpnPackage, lockdownEnabled);
+ return mService.setAlwaysOnVpnPackage(
+ userId, vpnPackage, lockdownEnabled, lockdownWhitelist);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1029,6 +1042,7 @@
* or {@code null} if none is set.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
public String getAlwaysOnVpnPackageForUser(int userId) {
try {
return mService.getAlwaysOnVpnPackage(userId);
@@ -1038,6 +1052,36 @@
}
/**
+ * @return whether always-on VPN is in lockdown mode.
+ *
+ * @hide
+ **/
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+ public boolean isVpnLockdownEnabled(int userId) {
+ try {
+ return mService.isVpnLockdownEnabled(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ }
+
+ /**
+ * @return the list of packages that are allowed to access network when always-on VPN is in
+ * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active.
+ *
+ * @hide
+ **/
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+ public List<String> getVpnLockdownWhitelist(int userId) {
+ try {
+ return mService.getVpnLockdownWhitelist(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns details about the currently active default data network
* for a given uid. This is for internal use only to avoid spying
* other apps.
@@ -1699,6 +1743,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 +1848,8 @@
/**
* Starts an IPsec NAT-T keepalive packet with the specified parameters.
*
+ * @deprecated Use {@link #createSocketKeepalive} instead.
+ *
* @hide
*/
@UnsupportedAppUsage
@@ -1821,6 +1869,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.
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 131925e..fd7360f 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -125,8 +125,11 @@
boolean updateLockdownVpn();
boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
- boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown);
+ boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown,
+ in List<String> lockdownWhitelist);
String getAlwaysOnVpnPackage(int userId);
+ boolean isVpnLockdownEnabled(int userId);
+ List<String> getVpnLockdownWhitelist(int userId);
int checkMobileProvisioning(int suggestedTimeOutMs);
@@ -181,6 +184,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();
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 21b6a8e..6628701 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -174,7 +174,8 @@
/**
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
+ @TestApi
public LinkProperties(LinkProperties source) {
if (source != null) {
mIfaceName = source.mIfaceName;
@@ -576,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) {
@@ -590,6 +593,8 @@
* this link.
* @hide
*/
+ @SystemApi
+ @TestApi
public List<InetAddress> getPcscfServers() {
return Collections.unmodifiableList(mPcscfs);
}
@@ -781,6 +786,8 @@
* @return the NAT64 prefix.
* @hide
*/
+ @SystemApi
+ @TestApi
public @Nullable IpPrefix getNat64Prefix() {
return mNat64Prefix;
}
@@ -794,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);
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/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/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 37bf3a7..dc099a4 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -509,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/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java
index 16aea31b..5b5a235 100644
--- a/core/java/android/net/metrics/IpConnectivityLog.java
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -18,7 +18,6 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics;
import android.net.Network;
@@ -51,7 +50,8 @@
public interface Event extends Parcelable {}
/** @hide */
- @UnsupportedAppUsage
+ @SystemApi
+ @TestApi
public IpConnectivityLog() {
}
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/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 5bf9095..efcad3e 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -369,26 +369,6 @@
}
/**
- * Attempt to setup ANGLE with a (temporary) default rules file: b/121153494
- * True: Rules file was loaded.
- * False: Rules file was *not* loaded.
- */
- private boolean setupAngleRulesDebug(String packageName, String paths, String devOptIn) {
- // b/121153494
- // Skip APK rules file checking.
- if (!DEBUG) {
- Log.v(TAG, "Skipping loading the rules file.");
- // Fill in some default values for now, so the loader can get an answer when it asks.
- // Most importantly, we need to indicate which app we are init'ing and what the
- // developer options for it are so we can turn on ANGLE if needed.
- setAngleInfo(paths, packageName, devOptIn, null, 0, 0);
- return true;
- }
-
- return false;
- }
-
- /**
* Attempt to setup ANGLE with a rules file loaded from the ANGLE APK.
* True: APK rules file was loaded.
* False: APK rules file was *not* loaded.
@@ -425,16 +405,57 @@
}
/**
+ * Pull ANGLE whitelist from GlobalSettings and compare against current package
+ */
+ private boolean checkAngleWhitelist(Bundle bundle, String packageName) {
+ List<String> angleWhitelist =
+ getGlobalSettingsString(bundle,
+ Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST);
+
+ return angleWhitelist.contains(packageName);
+ }
+
+ /**
* Pass ANGLE details down to trigger enable logic
*/
public void setupAngle(Context context, Bundle bundle, String packageName) {
- String devOptIn = getDriverForPkg(bundle, packageName);
+ if (packageName.isEmpty()) {
+ Log.v(TAG, "No package name available yet, skipping ANGLE setup");
+ return;
+ }
+ String devOptIn = getDriverForPkg(bundle, packageName);
if (DEBUG) {
Log.v(TAG, "ANGLE Developer option for '" + packageName + "' "
+ "set to: '" + devOptIn + "'");
}
+ // We only need to check rules if the app is whitelisted or the developer has
+ // explicitly chosen something other than default driver.
+ //
+ // The whitelist will be generated by the ANGLE APK at both boot time and
+ // ANGLE update time. It will only include apps mentioned in the rules file.
+ //
+ // If the user has set the developer option to something other than default,
+ // we need to call setupAngleRulesApk() with the package name and the developer
+ // option value (native/angle/other). Then later when we are actually trying to
+ // load a driver, GraphicsEnv::shouldUseAngle() has seen the package name before
+ // and can confidently answer yes/no based on the previously set developer
+ // option value.
+ boolean whitelisted = checkAngleWhitelist(bundle, packageName);
+ boolean defaulted = devOptIn.equals(sDriverMap.get(OpenGlDriverChoice.DEFAULT));
+ boolean rulesCheck = (whitelisted || !defaulted);
+ if (!rulesCheck) {
+ return;
+ }
+
+ if (whitelisted) {
+ Log.v(TAG, "ANGLE whitelist includes " + packageName);
+ }
+ if (!defaulted) {
+ Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn);
+ }
+
String anglePkgName = getAnglePackageName(context);
if (anglePkgName.isEmpty()) {
Log.e(TAG, "Failed to find ANGLE package.");
@@ -466,12 +487,6 @@
return;
}
- // b/121153494
- if (setupAngleRulesDebug(packageName, paths, devOptIn)) {
- // We setup ANGLE with defaults, so we're done here.
- return;
- }
-
if (setupAngleRulesApk(anglePkgName, angleInfo, context, packageName, paths, devOptIn)) {
// We setup ANGLE with rules from the APK, so we're done here.
return;
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 63912ec..630bd2e 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -54,6 +54,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
+import java.io.UncheckedIOException;
import java.net.DatagramSocket;
import java.net.Socket;
import java.nio.ByteOrder;
@@ -393,26 +394,41 @@
* @param socket The Socket whose FileDescriptor is used to create
* a new ParcelFileDescriptor.
*
- * @return A new ParcelFileDescriptor with the FileDescriptor of the
- * specified Socket.
+ * @return A new ParcelFileDescriptor with a duped copy of the
+ * FileDescriptor of the specified Socket.
+ *
+ * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException.
*/
public static ParcelFileDescriptor fromSocket(Socket socket) {
FileDescriptor fd = socket.getFileDescriptor$();
- return fd != null ? new ParcelFileDescriptor(fd) : null;
+ try {
+ return fd != null ? ParcelFileDescriptor.dup(fd) : null;
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
}
/**
- * Create a new ParcelFileDescriptor from the specified DatagramSocket.
+ * Create a new ParcelFileDescriptor from the specified DatagramSocket. The
+ * new ParcelFileDescriptor holds a dup of the original FileDescriptor in
+ * the DatagramSocket, so you must still close the DatagramSocket as well
+ * as the new ParcelFileDescriptor.
*
* @param datagramSocket The DatagramSocket whose FileDescriptor is used
* to create a new ParcelFileDescriptor.
*
- * @return A new ParcelFileDescriptor with the FileDescriptor of the
- * specified DatagramSocket.
+ * @return A new ParcelFileDescriptor with a duped copy of the
+ * FileDescriptor of the specified Socket.
+ *
+ * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException.
*/
public static ParcelFileDescriptor fromDatagramSocket(DatagramSocket datagramSocket) {
FileDescriptor fd = datagramSocket.getFileDescriptor$();
- return fd != null ? new ParcelFileDescriptor(fd) : null;
+ try {
+ return fd != null ? ParcelFileDescriptor.dup(fd) : null;
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
}
/**
@@ -542,7 +558,7 @@
}
file.deactivate();
FileDescriptor fd = file.getFileDescriptor();
- return fd != null ? new ParcelFileDescriptor(fd) : null;
+ return fd != null ? ParcelFileDescriptor.dup(fd) : null;
}
/**
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 07c4933..d2ab053 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -530,7 +530,7 @@
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 */
@@ -551,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/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/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 4cb9c5b..9e47179 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -76,6 +76,16 @@
/**
* @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";
/**
@@ -87,6 +97,15 @@
* The name of the secondary (alternate ABI) zygote socket.
*/
private final LocalSocketAddress mZygoteSecondarySocketAddress;
+ /**
+ * The name of the socket used to communicate with the primary blastula pool.
+ */
+ private final LocalSocketAddress mBlastulaPoolSocketAddress;
+
+ /**
+ * The name of the socket used to communicate with the secondary (alternate ABI) blastula pool.
+ */
+ private final LocalSocketAddress mBlastulaPoolSecondarySocketAddress;
public ZygoteProcess() {
mZygoteSocketAddress =
@@ -94,12 +113,22 @@
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 primarySocketAddress,
LocalSocketAddress secondarySocketAddress) {
mZygoteSocketAddress = primarySocketAddress;
mZygoteSecondarySocketAddress = secondarySocketAddress;
+
+ mBlastulaPoolSocketAddress = null;
+ mBlastulaPoolSecondarySocketAddress = null;
}
public LocalSocketAddress getPrimarySocketAddress() {
@@ -111,6 +140,7 @@
*/
public static class ZygoteState {
final LocalSocketAddress mZygoteSocketAddress;
+ final LocalSocketAddress mBlastulaSocketAddress;
private final LocalSocket mZygoteSessionSocket;
@@ -122,11 +152,13 @@
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;
@@ -134,14 +166,17 @@
}
/**
- * Create a new ZygoteState object by connecting to the given Zygote socket.
+ * 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)
+ public static ZygoteState connect(LocalSocketAddress zygoteSocketAddress,
+ LocalSocketAddress blastulaSocketAddress)
throws IOException {
DataInputStream zygoteInputStream = null;
@@ -154,7 +189,7 @@
zygoteOutputWriter =
new BufferedWriter(
new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
- 256);
+ Zygote.SOCKET_BUFFER_SIZE);
} catch (IOException ex) {
try {
zygoteSessionSocket.close();
@@ -163,11 +198,18 @@
throw ex;
}
- return new ZygoteState(zygoteSocketAddress,
+ return new ZygoteState(zygoteSocketAddress, blastulaSocketAddress,
zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter,
getAbiList(zygoteOutputWriter, zygoteInputStream));
}
+ LocalSocket getBlastulaSessionSocket() throws IOException {
+ final LocalSocket blastulaSessionSocket = new LocalSocket();
+ blastulaSessionSocket.connect(this.mBlastulaSocketAddress);
+
+ return blastulaSessionSocket;
+ }
+
boolean matches(String abi) {
return mABIList.contains(abi);
}
@@ -269,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, /*startChildZygote=*/false,
- packageName, packagesForUid, visibleVols, zygoteArgs);
+ packageName, packagesForUid, visibleVols, useBlastulaPool, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
@@ -322,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.mZygoteOutputWriter;
- final DataInputStream inputStream = zygoteState.mZygoteInputStream;
+ 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;
}
/**
@@ -416,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>();
@@ -522,7 +635,9 @@
}
synchronized(mLock) {
- return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
+ return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
+ useBlastulaPool,
+ argsForZygote);
}
}
@@ -686,7 +801,7 @@
if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
try {
primaryZygoteState =
- ZygoteState.connect(mZygoteSocketAddress);
+ ZygoteState.connect(mZygoteSocketAddress, mBlastulaPoolSocketAddress);
} catch (IOException ioe) {
throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
}
@@ -703,7 +818,8 @@
if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
try {
secondaryZygoteState =
- ZygoteState.connect(mZygoteSecondarySocketAddress);
+ ZygoteState.connect(mZygoteSecondarySocketAddress,
+ mBlastulaPoolSecondarySocketAddress);
} catch (IOException ioe) {
throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
}
@@ -820,7 +936,7 @@
for (int n = 20; n >= 0; n--) {
try {
final ZygoteState zs =
- ZygoteState.connect(zygoteSocketAddress);
+ ZygoteState.connect(zygoteSocketAddress, null);
zs.close();
return;
} catch (IOException ioe) {
@@ -884,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/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 87efbf3..cd823a9 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
@@ -104,6 +105,24 @@
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
@@ -121,6 +140,47 @@
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";
+ }
+
+ /**
+ * Namespace for activity manager related features. These features will be applied
+ * immediately upon change.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface ActivityManager {
+ String NAMESPACE = "activity_manager";
+
+ /**
+ * App compaction flags. See {@link com.android.server.am.AppCompactor}.
+ */
+ String KEY_USE_COMPACTION = "use_compaction";
+ String KEY_COMPACT_ACTION_1 = "compact_action_1";
+ String KEY_COMPACT_ACTION_2 = "compact_action_2";
+ String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
+ String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
+ String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
+ String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
+
+ /**
+ * Maximum number of cached processes. See
+ * {@link com.android.server.am.ActivityManagerConstants}.
+ */
+ String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
+ }
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
@@ -136,9 +196,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
@@ -160,14 +219,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)
@@ -186,9 +244,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)
@@ -205,12 +262,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)
@@ -242,9 +298,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(
@@ -345,8 +400,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/MediaStore.java b/core/java/android/provider/MediaStore.java
index 487198b..f5c442f 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -89,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";
/**
@@ -1566,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);
@@ -2743,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
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index fa3f54a..0961bc3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5800,6 +5800,16 @@
public static final String ALWAYS_ON_VPN_LOCKDOWN = "always_on_vpn_lockdown";
/**
+ * Comma separated list of packages that are allowed to access the network when VPN is in
+ * lockdown mode but not running.
+ * @see #ALWAYS_ON_VPN_LOCKDOWN
+ *
+ * @hide
+ */
+ public static final String ALWAYS_ON_VPN_LOCKDOWN_WHITELIST =
+ "always_on_vpn_lockdown_whitelist";
+
+ /**
* Whether applications can be installed for this user via the system's
* {@link Intent#ACTION_INSTALL_PACKAGE} mechanism.
*
@@ -7802,6 +7812,9 @@
* or an activity that handles ACTION_ASSIST, or empty which means using the default
* handling.
*
+ * <p>This should be set indirectly by setting the {@link
+ * android.app.role.RoleManager#ROLE_ASSISTANT assistant role}.
+ *
* @hide
*/
@UnsupportedAppUsage
@@ -8236,6 +8249,16 @@
private static final Validator NOTIFICATION_BADGING_VALIDATOR = BOOLEAN_VALIDATOR;
/**
+ * Whether notifications are dismissed by a right-to-left swipe (instead of a left-to-right
+ * swipe).
+ *
+ * @hide
+ */
+ public static final String NOTIFICATION_DISMISS_RTL = "notification_dismiss_rtl";
+
+ private static final Validator NOTIFICATION_DISMISS_RTL_VALIDATOR = BOOLEAN_VALIDATOR;
+
+ /**
* Comma separated list of QS tiles that have been auto-added already.
* @hide
*/
@@ -8421,6 +8444,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.
*
@@ -8516,6 +8560,7 @@
ASSIST_GESTURE_WAKE_ENABLED,
VR_DISPLAY_MODE,
NOTIFICATION_BADGING,
+ NOTIFICATION_DISMISS_RTL,
QS_AUTO_ADDED_TILES,
SCREENSAVER_ENABLED,
SCREENSAVER_COMPONENTS,
@@ -8544,6 +8589,8 @@
LOCK_SCREEN_WHEN_TRUST_LOST,
SKIP_GESTURE,
SILENCE_GESTURE,
+ THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ AWARE_ENABLED,
};
/**
@@ -8676,6 +8723,7 @@
VALIDATORS.put(ASSIST_GESTURE_WAKE_ENABLED, ASSIST_GESTURE_WAKE_ENABLED_VALIDATOR);
VALIDATORS.put(VR_DISPLAY_MODE, VR_DISPLAY_MODE_VALIDATOR);
VALIDATORS.put(NOTIFICATION_BADGING, NOTIFICATION_BADGING_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_DISMISS_RTL, NOTIFICATION_DISMISS_RTL_VALIDATOR);
VALIDATORS.put(QS_AUTO_ADDED_TILES, QS_AUTO_ADDED_TILES_VALIDATOR);
VALIDATORS.put(SCREENSAVER_ENABLED, SCREENSAVER_ENABLED_VALIDATOR);
VALIDATORS.put(SCREENSAVER_COMPONENTS, SCREENSAVER_COMPONENTS_VALIDATOR);
@@ -8714,6 +8762,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);
}
/**
@@ -9452,23 +9503,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.
*
@@ -9493,6 +9527,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
*/
@@ -12121,6 +12163,13 @@
"angle_gl_driver_selection_values";
/**
+ * List of package names that should check ANGLE rules
+ * @hide
+ */
+ public static final String GLOBAL_SETTINGS_ANGLE_WHITELIST =
+ "angle_whitelist";
+
+ /**
* Game Update Package global preference for all Apps.
* 0 = Default
* 1 = All Apps use Game Update Package
@@ -12150,6 +12199,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
@@ -12194,6 +12251,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
@@ -12205,7 +12287,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);
@@ -12959,28 +13040,6 @@
"sms_access_restriction_enabled";
/**
- * If set to 1, an app must have the READ_PRIVILEGED_PHONE_STATE permission (or be a device
- * / profile owner with the READ_PHONE_STATE permission) to access device identifiers.
- *
- * STOPSHIP: Remove this once we ship with the new device identifier check enabled.
- *
- * @hide
- */
- public static final String PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED =
- "privileged_device_identifier_check_enabled";
-
- /**
- * If set to 1, an app that is targeting Q and does not meet the new requirements to access
- * device identifiers will receive a SecurityException.
- *
- * STOPSHIP: Remove this once we ship with the new device identifier check enabled.
- *
- * @hide
- */
- public static final String PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED =
- "privileged_device_identifier_target_q_behavior_enabled";
-
- /**
* If set to 1, the device identifier check will be relaxed to the previous READ_PHONE_STATE
* permission check for 3P apps.
*
@@ -13003,6 +13062,17 @@
"privileged_device_identifier_non_priv_check_relaxed";
/**
+ * If set to 1, the device identifier check will be relaxed to the previous READ_PHONE_STATE
+ * permission check for preloaded privileged apps.
+ *
+ * STOPSHIP: Remove this once we ship with the new device identifier check enabled.
+ *
+ * @hide
+ */
+ public static final String PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED =
+ "privileged_device_identifier_priv_check_relaxed";
+
+ /**
* If set to 1, SettingsProvider's restoreAnyVersion="true" attribute will be ignored
* and restoring to lower version of platform API will be skipped.
*
@@ -13196,6 +13266,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,
@@ -13234,6 +13306,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);
@@ -13748,6 +13824,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
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/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/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/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/View.java b/core/java/android/view/View.java
index 2014ec2..991b385 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3985,6 +3985,15 @@
public static final int SCROLL_AXIS_VERTICAL = 1 << 1;
/**
+ * If a MotionEvent has CLASSIFICATION_AMBIGUOUS_GESTURE set, then certain the default
+ * long press action will be inhibited. However, to account for the possibility of incorrect
+ * classification, the default long press timeout will instead be increased for some situations
+ * by the following factor.
+ * Likewise, the touch slop for allowing long press will be increased when gesture is uncertain.
+ */
+ private static final int AMBIGUOUS_GESTURE_MULTIPLIER = 2;
+
+ /**
* Controls the over-scroll mode for this view.
* See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)},
* {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS},
@@ -8203,10 +8212,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>
@@ -8608,7 +8617,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) {
@@ -13994,7 +14003,7 @@
if (clickable) {
setPressed(true, x, y);
}
- checkForLongClick(0, x, y);
+ checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
return true;
}
}
@@ -14735,7 +14744,7 @@
mHasPerformedLongPress = false;
if (!clickable) {
- checkForLongClick(0, x, y);
+ checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
break;
}
@@ -14759,7 +14768,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;
@@ -14780,8 +14789,27 @@
drawableHotspotChanged(x, y);
}
+ final int motionClassification = event.getClassification();
+ final boolean ambiguousGesture =
+ motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
+ int touchSlop = mTouchSlop;
+ if (ambiguousGesture && hasPendingLongPressCallback()) {
+ if (!pointInView(x, y, touchSlop)) {
+ // The default action here is to cancel long press. But instead, we
+ // just extend the timeout here, in case the classification
+ // stays ambiguous.
+ removeLongPressCallback();
+ long delay = ViewConfiguration.getLongPressTimeout()
+ * AMBIGUOUS_GESTURE_MULTIPLIER;
+ // Subtract the time already spent
+ delay -= event.getEventTime() - event.getDownTime();
+ checkForLongClick(delay, x, y);
+ }
+ touchSlop *= AMBIGUOUS_GESTURE_MULTIPLIER;
+ }
+
// Be lenient about moving outside of buttons
- if (!pointInView(x, y, mTouchSlop)) {
+ if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
@@ -14791,6 +14819,15 @@
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
+
+ final boolean deepPress =
+ motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
+ if (deepPress && hasPendingLongPressCallback()) {
+ // process the long click action immediately
+ removeLongPressCallback();
+ checkForLongClick(0 /* send immediately */, x, y);
+ }
+
break;
}
@@ -14825,6 +14862,21 @@
}
/**
+ * Return true if the long press callback is scheduled to run sometime in the future.
+ * Return false if there is no scheduled long press callback at the moment.
+ */
+ private boolean hasPendingLongPressCallback() {
+ if (mPendingCheckForLongPress == null) {
+ return false;
+ }
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo == null) {
+ return false;
+ }
+ return attachInfo.mHandler.hasCallbacks(mPendingCheckForLongPress);
+ }
+
+ /**
* Remove the pending click action
*/
@UnsupportedAppUsage
@@ -25434,7 +25486,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;
@@ -25444,8 +25496,7 @@
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
- postDelayed(mPendingCheckForLongPress,
- ViewConfiguration.getLongPressTimeout() - delayOffset);
+ postDelayed(mPendingCheckForLongPress, delay);
}
}
@@ -27035,7 +27086,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/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index a35be27..b708323 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.WindowInsets.Type.ime;
+
import android.annotation.NonNull;
import android.view.WindowInsets.Type.InsetType;
@@ -32,11 +34,11 @@
* <p>
* Note that if the window currently doesn't have control over a certain type, it will apply the
* change as soon as the window gains control. The app can listen to the event by observing
- * {@link View#onApplyWindowInsets} and checking visibility with "TODO at method" in
- * {@link WindowInsets}.
+ * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}.
*
* @param types A bitmask of {@link WindowInsets.Type.InsetType} specifying what windows the app
* would like to make appear on screen.
+ * @hide
*/
void show(@InsetType int types);
@@ -45,11 +47,11 @@
* <p>
* Note that if the window currently doesn't have control over a certain type, it will apply the
* change as soon as the window gains control. The app can listen to the event by observing
- * {@link View#onApplyWindowInsets} and checking visibility with "TODO at method" in
- * {@link WindowInsets}.
+ * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}.
*
* @param types A bitmask of {@link WindowInsets.Type.InsetType} specifying what windows the app
* would like to make disappear.
+ * @hide
*/
void hide(@InsetType int types);
@@ -60,7 +62,50 @@
* @param types The {@link InsetType}s the application has requested to control.
* @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
* windows are ready to be controlled, among other callbacks.
+ * @hide
*/
void controlWindowInsetsAnimation(@InsetType int types,
@NonNull WindowInsetsAnimationControlListener listener);
+
+ /**
+ * Lets the application control the animation for showing the IME in a frame-by-frame manner by
+ * modifying the position of the IME when it's causing insets.
+ *
+ * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
+ * IME are ready to be controlled, among other callbacks.
+ */
+ default void controlInputMethodAnimation(
+ @NonNull WindowInsetsAnimationControlListener listener) {
+ controlWindowInsetsAnimation(ime(), listener);
+ }
+
+ /**
+ * Makes the IME appear on screen.
+ * <p>
+ * Note that if the window currently doesn't have control over the IME, because it doesn't have
+ * focus, it will apply the change as soon as the window gains control. The app can listen to
+ * the event by observing {@link View#onApplyWindowInsets} and checking visibility with
+ * {@link WindowInsets#isVisible}.
+ *
+ * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener)
+ * @see #hideInputMethod()
+ */
+ default void showInputMethod() {
+ show(ime());
+ }
+
+ /**
+ * Makes the IME disappear on screen.
+ * <p>
+ * Note that if the window currently doesn't have control over IME, because it doesn't have
+ * focus, it will apply the change as soon as the window gains control. The app can listen to
+ * the event by observing {@link View#onApplyWindowInsets} and checking visibility with
+ * {@link WindowInsets#isVisible}.
+ *
+ * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener)
+ * @see #showInputMethod()
+ */
+ default void hideInputMethod() {
+ hide(ime());
+ }
}
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..c425e7b 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;
@@ -34,8 +34,6 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
-import dalvik.system.CloseGuard;
-
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -148,9 +146,6 @@
@Retention(RetentionPolicy.SOURCE)
@interface FlushReason{}
-
- private final CloseGuard mCloseGuard = CloseGuard.get();
-
private final Object mLock = new Object();
/**
@@ -185,7 +180,6 @@
@VisibleForTesting
public ContentCaptureSession(@NonNull String id) {
mId = Preconditions.checkNotNull(id);
- mCloseGuard.open("destroy");
}
/** @hide */
@@ -246,13 +240,11 @@
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;
- mCloseGuard.close();
-
// TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
// id) and send it to the cache of batched commands
if (VERBOSE) {
@@ -288,18 +280,6 @@
destroy();
}
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mCloseGuard != null) {
- mCloseGuard.warnIfOpen();
- }
- destroy();
- } finally {
- super.finalize();
- }
- }
-
/**
* Notifies the Content Capture Service that a node has been added to the view structure.
*
@@ -352,14 +332,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 +385,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 +403,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/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl b/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl
new file mode 100644
index 0000000..fbe47e0
--- /dev/null
+++ b/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentcapture;
+
+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 15088d2..0cb1800 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2490,7 +2490,14 @@
* @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) {
if (Process.myUid() == Process.SYSTEM_UID) {
@@ -2675,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/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
index ed86206..10c7ade 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -73,9 +73,16 @@
/**
* Returns the text classifier that was set via {@link #setTextClassifier(TextClassifier)}.
* If this is null, this method returns a default text classifier (i.e. either the system text
- * classifier if one exists, or a local text classifier running in this app.)
+ * classifier if one exists, or a local text classifier running in this process.)
+ * <p>
+ * Note that if system textclassifier is in use, requests will be sent to a textclassifier
+ * package provided from OEM. If you want to make sure the requests are handled in your own
+ * process, you should consider {@link #getLocalTextClassifier()} instead. However, the local
+ * textclassifier may return inferior results to those returned by the system
+ * textclassifier.
*
* @see #setTextClassifier(TextClassifier)
+ * @see #getLocalTextClassifier()
*/
@NonNull
public TextClassifier getTextClassifier() {
@@ -215,7 +222,13 @@
return TextClassifier.NO_OP;
}
- private TextClassifier getLocalTextClassifier() {
+ /**
+ * Returns a local textclassifier, which is running in this process.
+ *
+ * @see #getTextClassifier()
+ */
+ @NonNull
+ public TextClassifier getLocalTextClassifier() {
synchronized (mLock) {
if (mLocalTextClassifier == null) {
if (getSettings().isLocalTextClassifierEnabled()) {
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/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 3a1c457..de1f3df 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -150,6 +150,7 @@
}
try {
+ String abi = sPackage.applicationInfo.primaryCpuAbi;
sZygote = Process.ZYGOTE_PROCESS.startChildZygote(
"com.android.internal.os.WebViewZygoteInit",
"webview_zygote",
@@ -158,39 +159,40 @@
null, // gids
0, // runtimeFlags
"webview_zygote", // seInfo
- sPackage.applicationInfo.primaryCpuAbi, // abi
+ abi, // abi
TextUtils.join(",", Build.SUPPORTED_ABIS),
null, // instructionSet
Process.FIRST_ISOLATED_UID,
Process.LAST_ISOLATED_UID);
-
- // All the work below is usually done by LoadedApk, but the zygote can't talk to
- // PackageManager or construct a LoadedApk since it's single-threaded pre-fork, so
- // doesn't have an ActivityThread and can't use Binder.
- // Instead, figure out the paths here, in the system server where we have access to
- // the package manager. Reuse the logic from LoadedApk to determine the correct
- // paths and pass them to the zygote as strings.
- final List<String> zipPaths = new ArrayList<>(10);
- final List<String> libPaths = new ArrayList<>(10);
- LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths);
- final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
- final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
- TextUtils.join(File.pathSeparator, zipPaths);
-
- String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo);
-
- // In the case where the ApplicationInfo has been modified by the stub WebView,
- // we need to use the original ApplicationInfo to determine what the original classpath
- // would have been to use as a cache key.
- LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null);
- final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) :
- TextUtils.join(File.pathSeparator, zipPaths);
-
ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress());
- Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
- sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey,
- Build.SUPPORTED_ABIS[0]);
+ if (sPackageOriginalAppInfo.sourceDir.equals(sPackage.applicationInfo.sourceDir)) {
+ // No stub WebView is involved here, so we can preload the package the "clean" way
+ // using the ApplicationInfo.
+ sZygote.preloadApp(sPackage.applicationInfo, abi);
+ } else {
+ // Legacy path to support the stub WebView.
+ // Reuse the logic from LoadedApk to determine the correct paths and pass them to
+ // the zygote as strings.
+ final List<String> zipPaths = new ArrayList<>(10);
+ final List<String> libPaths = new ArrayList<>(10);
+ LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths);
+ final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
+ final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
+ TextUtils.join(File.pathSeparator, zipPaths);
+
+ String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo);
+
+ // Use the original ApplicationInfo to determine what the original classpath would
+ // have been to use as a cache key.
+ LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null);
+ final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) :
+ TextUtils.join(File.pathSeparator, zipPaths);
+
+ Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
+ sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey,
+ Build.SUPPORTED_ABIS[0]);
+ }
} catch (Exception e) {
Log.e(LOGTAG, "Error connecting to webview zygote", e);
stopZygoteLocked();
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/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index 7c371cb..d0102a7 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -17,13 +17,10 @@
package com.android.internal.app;
import android.annotation.NonNull;
-import android.app.SearchManager;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -31,8 +28,6 @@
import android.provider.Settings;
import android.util.Log;
-import com.android.internal.R;
-
import java.util.ArrayList;
import java.util.Set;
@@ -44,14 +39,6 @@
private static final String TAG = "AssistUtils";
- /**
- * Sentinel value for "no default assistant specified."
- *
- * Empty string is already used to represent an explicit setting of No Assistant. null cannot
- * be used because we can't represent a null value in XML.
- */
- private static final String UNSET = "#+UNSET";
-
private final Context mContext;
private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
@@ -186,37 +173,9 @@
Settings.Secure.ASSISTANT, userId);
if (setting != null) {
return ComponentName.unflattenFromString(setting);
- }
-
- final String defaultSetting = mContext.getResources().getString(
- R.string.config_defaultAssistantComponentName);
- if (defaultSetting != null && !defaultSetting.equals(UNSET)) {
- return ComponentName.unflattenFromString(defaultSetting);
- }
-
- // Fallback to keep backward compatible behavior when there is no user setting.
- if (activeServiceSupportsAssistGesture()) {
- return getActiveServiceComponentName();
- }
-
- if (UNSET.equals(defaultSetting)) {
+ } else {
return null;
}
-
- final SearchManager searchManager =
- (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
- if (searchManager == null) {
- return null;
- }
- final Intent intent = searchManager.getAssistIntent(false);
- PackageManager pm = mContext.getPackageManager();
- ResolveInfo info = pm.resolveActivityAsUser(intent, PackageManager.MATCH_DEFAULT_ONLY,
- userId);
- if (info != null) {
- return new ComponentName(info.activityInfo.applicationInfo.packageName,
- info.activityInfo.name);
- }
- return null;
}
public static boolean isPreinstalledAssistant(Context context, ComponentName assistant) {
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/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index 9bacf9b..f848346 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -64,6 +64,9 @@
private boolean mUseBpfStats;
+ // A persistent Snapshot since device start for eBPF stats
+ private final NetworkStats mPersistSnapshot;
+
// TODO: only do adjustments in NetworkStatsService and remove this.
/**
* (Stacked interface) -> (base interface) association for all connected ifaces since boot.
@@ -135,6 +138,7 @@
mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
mUseBpfStats = useBpfStats;
+ mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
}
public NetworkStats readBpfNetworkStatsDev() throws IOException {
@@ -268,6 +272,7 @@
return stats;
}
+ // TODO: delete the lastStats parameter
private NetworkStats readNetworkStatsDetailInternal(int limitUid, String[] limitIfaces,
int limitTag, NetworkStats lastStats) throws IOException {
if (USE_NATIVE_PARSING) {
@@ -278,16 +283,28 @@
} else {
stats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
}
- if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid,
- limitIfaces, limitTag, mUseBpfStats) != 0) {
- throw new IOException("Failed to parse network stats");
+ if (mUseBpfStats) {
+ if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL,
+ null, TAG_ALL, mUseBpfStats) != 0) {
+ throw new IOException("Failed to parse network stats");
+ }
+ mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime());
+ mPersistSnapshot.combineAllValues(stats);
+ NetworkStats result = mPersistSnapshot.clone();
+ result.filter(limitUid, limitIfaces, limitTag);
+ return result;
+ } else {
+ if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid,
+ limitIfaces, limitTag, mUseBpfStats) != 0) {
+ throw new IOException("Failed to parse network stats");
+ }
+ if (SANITY_CHECK_NATIVE) {
+ final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid,
+ limitIfaces, limitTag);
+ assertEquals(javaStats, stats);
+ }
+ return stats;
}
- if (SANITY_CHECK_NATIVE) {
- final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid,
- limitIfaces, limitTag);
- assertEquals(javaStats, stats);
- }
- return stats;
} else {
return javaReadNetworkStatsDetail(mStatsXtUid, limitUid, limitIfaces, limitTag);
}
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/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 6454352..2c272de 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -49,7 +49,7 @@
* per thread, uid or call description.
*/
public class BinderCallsStats implements BinderInternal.Observer {
- public static final boolean ENABLED_DEFAULT = false;
+ public static final boolean ENABLED_DEFAULT = true;
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;
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index 0b329d7..c8d30b2 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -17,8 +17,9 @@
package com.android.internal.os;
import android.app.ApplicationLoaders;
+import android.app.LoadedApk;
+import android.content.pm.ApplicationInfo;
import android.net.LocalSocket;
-import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.WebViewFactory;
@@ -66,6 +67,34 @@
}
@Override
+ protected boolean canPreloadApp() {
+ return true;
+ }
+
+ @Override
+ protected void handlePreloadApp(ApplicationInfo appInfo) {
+ Log.i(TAG, "Beginning application preload for " + appInfo.packageName);
+ LoadedApk loadedApk = new LoadedApk(null, appInfo, null, null, false, true, false);
+ ClassLoader loader = loadedApk.getClassLoader();
+ doPreload(loader, WebViewFactory.getWebViewLibrary(appInfo));
+
+ // Add the APK to the Zygote's list of allowed files for children.
+ Zygote.nativeAllowFileAcrossFork(appInfo.sourceDir);
+ if (appInfo.splitSourceDirs != null) {
+ for (String path : appInfo.splitSourceDirs) {
+ Zygote.nativeAllowFileAcrossFork(path);
+ }
+ }
+ if (appInfo.sharedLibraryFiles != null) {
+ for (String path : appInfo.sharedLibraryFiles) {
+ Zygote.nativeAllowFileAcrossFork(path);
+ }
+ }
+
+ Log.i(TAG, "Application preload done");
+ }
+
+ @Override
protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName,
String cacheKey) {
Log.i(TAG, "Beginning package preload");
@@ -76,16 +105,22 @@
ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader(
packagePath, libsPath, cacheKey);
- // Load the native library using WebViewLibraryLoader to share the RELRO data with other
- // processes.
- WebViewLibraryLoader.loadNativeLibrary(loader, libFileName);
-
// Add the APK to the Zygote's list of allowed files for children.
String[] packageList = TextUtils.split(packagePath, File.pathSeparator);
for (String packageEntry : packageList) {
Zygote.nativeAllowFileAcrossFork(packageEntry);
}
+ doPreload(loader, libFileName);
+
+ Log.i(TAG, "Package preload done");
+ }
+
+ private void doPreload(ClassLoader loader, String libFileName) {
+ // Load the native library using WebViewLibraryLoader to share the RELRO data with other
+ // processes.
+ WebViewLibraryLoader.loadNativeLibrary(loader, libFileName);
+
// Once we have the classloader, look up the WebViewFactoryProvider implementation and
// call preloadInZygote() on it to give it the opportunity to preload the native library
// and perform any other initialisation work that should be shared among the children.
@@ -114,8 +149,6 @@
} catch (IOException ioe) {
throw new IllegalStateException("Error writing to command socket", ioe);
}
-
- Log.i(TAG, "Package preload done");
}
}
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index f609f2f..069413f 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -117,7 +117,13 @@
/** 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 */
+ /**
+ * 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;
/**
@@ -187,6 +193,11 @@
// 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() */
@@ -234,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);
+ packagesForUID, visibleVolIDs);
// Enable tracing as soon as possible for the child process.
if (pid == 0) {
Trace.setTracingEnabled(true, runtimeFlags);
@@ -256,7 +267,7 @@
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);
+ String appDataDir, String packageName, String[] packagesForUID, String[] visibleVolIDs);
/**
* Specialize a Blastula instance. The current VM must have been started
@@ -281,12 +292,12 @@
*/
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) {
+ 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);
+ packageName, packagesForUID, visibleVolIDs);
// Enable tracing as soon as possible for the child process.
Trace.setTracingEnabled(true, runtimeFlags);
@@ -306,7 +317,7 @@
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);
+ String[] packagesForUID, String[] visibleVolIDs);
/**
* Called to do any initialization before starting an application.
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index df89b26..24a08ca 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -339,6 +339,8 @@
mMountExternal = Zygote.MOUNT_EXTERNAL_FULL;
} else if (arg.equals("--mount-external-installer")) {
mMountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER;
+ } else if (arg.equals("--mount-external-legacy")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_LEGACY;
} else if (arg.equals("--query-abi-list")) {
mAbiListQuery = true;
} else if (arg.equals("--get-pid")) {
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 78ecee1..ffbe8eb 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -242,7 +242,7 @@
fdsToClose[0] = fd.getInt$();
}
- fd = zygoteServer.getServerSocketFileDescriptor();
+ fd = zygoteServer.getZygoteSocketFileDescriptor();
if (fd != null) {
fdsToClose[1] = fd.getInt$();
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 2f00c07..e3e55ed 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -269,7 +269,7 @@
try {
BufferedReader br =
- new BufferedReader(new InputStreamReader(is), 256);
+ new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);
int count = 0;
String line;
@@ -750,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"))) {
@@ -786,7 +786,17 @@
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) {
@@ -829,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;
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index c1bfde1..a78c095 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -26,6 +26,8 @@
import android.util.Log;
import android.util.Slog;
+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.
@@ -70,31 +71,17 @@
}
/**
- * 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;
}
}
@@ -103,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(
@@ -120,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);
@@ -138,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);
}
@@ -151,7 +138,7 @@
Log.e(TAG, "Zygote: error closing descriptor", ex);
}
- mServerSocket = null;
+ mZygoteSocket = null;
}
/**
@@ -160,8 +147,8 @@
* closure after a child process is forked off.
*/
- FileDescriptor getServerSocketFileDescriptor() {
- return mServerSocket.getFileDescriptor();
+ FileDescriptor getZygoteSocketFileDescriptor() {
+ return mZygoteSocket.getFileDescriptor();
}
/**
@@ -170,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.getFileDescriptor());
- } 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) {
@@ -217,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) {
@@ -234,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()
@@ -254,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/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/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/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 18d9b5a..f458299 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -152,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);
@@ -1370,6 +1372,8 @@
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),
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index ad51c47..5de0883 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -19,6 +19,7 @@
#include <hwui/Paint.h>
#include <hwui/Bitmap.h>
#include <renderthread/RenderProxy.h>
+#include <utils/Color.h>
#include <android_runtime/android_hardware_HardwareBuffer.h>
@@ -602,6 +603,14 @@
return static_cast<jint>(bitmap->getGenerationID());
}
+static jboolean Bitmap_isConfigF16(JNIEnv* env, jobject, jlong bitmapHandle) {
+ LocalScopedBitmap bitmap(bitmapHandle);
+ if (bitmap->info().colorType() == kRGBA_F16_SkColorType) {
+ return JNI_TRUE;
+ }
+ return JNI_FALSE;
+}
+
static jboolean Bitmap_isPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle) {
LocalScopedBitmap bitmap(bitmapHandle);
if (bitmap->info().alphaType() == kPremul_SkAlphaType) {
@@ -1120,7 +1129,8 @@
sp<GraphicBuffer> buffer(graphicBufferForJavaObject(env, graphicBuffer));
// To support any color space, we need to pass an additional ColorSpace argument to
// java Bitmap.createHardwareBitmap.
- sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, SkColorSpace::MakeSRGB());
+ SkColorType ct = uirenderer::PixelFormatToColorType(buffer->getPixelFormat());
+ sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, ct, SkColorSpace::MakeSRGB());
if (!bitmap.get()) {
ALOGW("failed to create hardware bitmap from graphic buffer");
return NULL;
@@ -1133,7 +1143,8 @@
AHardwareBuffer* hwBuf = android_hardware_HardwareBuffer_getNativeHardwareBuffer(env,
hardwareBuffer);
sp<GraphicBuffer> buffer(AHardwareBuffer_to_GraphicBuffer(hwBuf));
- sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer,
+ SkColorType ct = uirenderer::PixelFormatToColorType(buffer->getPixelFormat());
+ sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, ct,
GraphicsJNI::getNativeColorSpace(colorSpacePtr));
if (!bitmap.get()) {
ALOGW("failed to create hardware bitmap from hardware buffer");
@@ -1193,6 +1204,7 @@
{ "nativeErase", "(JJJ)V", (void*)Bitmap_eraseLong },
{ "nativeRowBytes", "(J)I", (void*)Bitmap_rowBytes },
{ "nativeConfig", "(J)I", (void*)Bitmap_config },
+ { "nativeIsConfigF16", "(J)Z", (void*)Bitmap_isConfigF16 },
{ "nativeHasAlpha", "(J)Z", (void*)Bitmap_hasAlpha },
{ "nativeIsPremultiplied", "(J)Z", (void*)Bitmap_isPremultiplied},
{ "nativeSetHasAlpha", "(JZZ)V", (void*)Bitmap_setHasAlpha},
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_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 0453195..69877c7 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -360,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();
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 318ec9b..4052919 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -1038,8 +1038,9 @@
// Continue I guess?
}
+ SkColorType ct = uirenderer::PixelFormatToColorType(buffer->getPixelFormat());
sk_sp<SkColorSpace> cs = uirenderer::DataSpaceToColorSpace(bufferItem.mDataSpace);
- sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs);
+ sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, ct, cs);
return bitmap::createBitmap(env, bitmap.release(),
android::bitmap::kBitmapCreateFlag_Premultiplied);
}
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 2e7184b..8681d4b 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1472,7 +1472,7 @@
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);
+ fds_to_close.push_back(gBlastulaPoolSocketFD);
if (gBlastulaPoolEventFD != -1) {
fds_to_close.push_back(gBlastulaPoolEventFD);
@@ -1498,7 +1498,7 @@
std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()),
fds_to_ignore(fds_to_close);
-// fds_to_close.push_back(gBlastulaPoolSocketFD);
+ fds_to_close.push_back(gBlastulaPoolSocketFD);
if (gBlastulaPoolEventFD != -1) {
fds_to_close.push_back(gBlastulaPoolEventFD);
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 53dde80..4e48663 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -72,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)
@@ -79,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
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 6cdba33..eb716ac 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2183,4 +2183,10 @@
// 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/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 4158577..f06165c 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -449,6 +449,10 @@
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;
+ // ANGLE - List of Apps that can check ANGLE rules
+ optional SettingProto angle_whitelist = 13;
}
optional Gpu gpu = 59;
@@ -535,6 +539,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 +745,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 e68f9db..188769d 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -31,6 +31,7 @@
import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
+// Next tag: 21
message JobSchedulerServiceDumpProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -139,9 +140,13 @@
// The current limit on the number of concurrent JobServiceContext entries
// we want to keep actively running a job.
optional int32 max_active_jobs = 13;
+
+ // Dump from JobConcurrencyManager.
+ optional JobConcurrencyManagerProto concurrency_manager = 20;
}
// A com.android.server.job.JobSchedulerService.Constants object.
+// Next tag: 29
message ConstantsProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -273,7 +278,39 @@
}
optional QuotaController quota_controller = 24;
- // Next tag: 26
+ // Max number of jobs, when screen is ON.
+ optional MaxJobCountsPerMemoryTrimLevelProto max_job_counts_screen_on = 26;
+
+ // Max number of jobs, when screen is OFF.
+ optional MaxJobCountsPerMemoryTrimLevelProto max_job_counts_screen_off = 27;
+
+ // In this time after screen turns on, we increase job concurrency.
+ optional int32 screen_off_job_concurrency_increase_delay_ms = 28;
+}
+
+// Next tag: 4
+message MaxJobCountsProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Total number of jobs to run simultaneously.
+ optional int32 total_jobs = 1;
+
+ // Max number of BG (== owned by non-TOP apps) jobs to run simultaneously.
+ optional int32 max_bg = 2;
+
+ // We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any
+ // pending, rather than always running the TOTAL number of FG jobs.
+ optional int32 min_bg = 3;
+}
+
+// Next tag: 5
+message MaxJobCountsPerMemoryTrimLevelProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional MaxJobCountsProto normal = 1;
+ optional MaxJobCountsProto moderate = 2;
+ optional MaxJobCountsProto low = 3;
+ optional MaxJobCountsProto critical = 4;
}
message StateControllerProto {
@@ -807,3 +844,46 @@
// Next tag: 28
}
+
+// Dump from com.android.server.job.JobConcurrencyManager.
+// Next tag: 7
+message JobConcurrencyManagerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Whether the device is interactive (== screen on) now or not.
+ optional bool current_interactive = 1;
+ // Similar to current_interactive, screen on or not, but it takes into account the off timeout.
+ optional bool effective_interactive = 2;
+ // How many milliseconds have passed since the last screen on. (i.e. 1000 == 1 sec ago)
+ optional int64 time_since_last_screen_on_ms = 3;
+ // How many milliseconds have passed since the last screen off. (i.e. 1000 == 1 sec ago)
+ optional int64 time_since_last_screen_off_ms = 4;
+ // Current max number of jobs.
+ optional JobCountTrackerProto job_count_tracker = 5;
+ // Current memory trim level.
+ optional int32 memory_trim_level = 6;
+}
+
+// Dump from com.android.server.job.JobConcurrencyManager.JobCountTracker.
+// Next tag: 8
+message JobCountTrackerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Number of total jos that can run simultaneously.
+ optional int32 config_num_max_total_jobs = 1;
+ // Number of background jos that can run simultaneously.
+ optional int32 config_num_max_bg_jobs = 2;
+ // Out of total jobs, this many background jobs should be guaranteed to be executed, even if
+ // there are the config_num_max_total_jobs count of foreground jobs pending.
+ optional int32 config_num_min_bg_jobs = 3;
+
+ // Number of running foreground jobs.
+ optional int32 num_running_fg_jobs = 4;
+ // Number of running background jobs.
+ optional int32 num_running_bg_jobs = 5;
+
+ // Number of pending foreground jobs.
+ optional int32 num_pending_fg_jobs = 6;
+ // Number of pending background jobs.
+ optional int32 num_pending_bg_jobs = 7;
+}
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/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 96b8dc2..6f7312f 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 -->
<!-- ======================================= -->
@@ -3522,6 +3528,12 @@
android:protectionLevel="signature|privileged" />
<uses-permission android:name="android.permission.CONTROL_VPN" />
+ <!-- Allows an application to access and modify always-on VPN configuration.
+ <p>Not for use by third-party or privileged applications.
+ @hide -->
+ <permission android:name="android.permission.CONTROL_ALWAYS_ON_VPN"
+ android:protectionLevel="signature" />
+
<!-- Allows an application to capture audio output.
<p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"
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 f9ec10d..49f2c84 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>
@@ -3726,9 +3729,6 @@
<!-- Whether or not the "SMS app service" feature is enabled -->
<bool name="config_useSmsAppService">true</bool>
- <!-- Component name for default assistant on this device -->
- <string name="config_defaultAssistantComponentName">#+UNSET</string>
-
<!-- Class name for the InputEvent compatibility processor override.
Empty string means use the default compatibility processor
(android.view.InputEventCompatProcessor). -->
@@ -3759,4 +3759,7 @@
<!-- 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..ec1bac1 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">
@@ -2986,7 +2988,7 @@
</public-group>
<public-group type="array" first-id="0x01070006">
- <!-- @hide @SystemApi -->
+ <!-- @hide @TestApi @SystemApi -->
<public name="config_defaultRoleHolders" />
</public-group>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f2b4b9c..a761baf 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -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 83982c7..d556130 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" />
@@ -2104,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" />
@@ -3522,8 +3528,6 @@
<java-symbol type="bool" name="config_useSmsAppService" />
- <java-symbol type="string" name="config_defaultAssistantComponentName" />
-
<java-symbol type="id" name="transition_overlay_view_tag" />
<java-symbol type="dimen" name="rounded_corner_radius" />
@@ -3548,4 +3552,6 @@
<!-- 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/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/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 1431741..9b79e85 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -298,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,
@@ -388,12 +389,9 @@
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,
- Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED,
+ Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED,
Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED,
Settings.Global.PROVISIONING_APN_ALARM_DELAY_IN_MS,
Settings.Global.RADIO_BLUETOOTH,
@@ -483,10 +481,12 @@
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE,
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS,
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES,
+ Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST,
Settings.Global.GUP_DEV_ALL_APPS,
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,
@@ -564,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,
@@ -572,6 +574,7 @@
Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
Settings.Secure.ALWAYS_ON_VPN_APP,
Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
+ Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST,
Settings.Secure.ANDROID_ID,
Settings.Secure.ANR_SHOW_BACKGROUND,
Settings.Secure.ASSISTANT,
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/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/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/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index bfbdbc5..8636949 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1711,20 +1711,22 @@
*/
@Nullable
public final ColorSpace getColorSpace() {
- // A reconfigure can change the configuration and rgba16f is
- // always linear scRGB at this time
- if (getConfig() == Config.RGBA_F16) {
- // Reset the color space for potential future reconfigurations
- mColorSpace = null;
- return ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
- }
-
+ checkRecycled("getColorSpace called on a recycled bitmap");
// Cache the color space retrieval since it can be fairly expensive
if (mColorSpace == null) {
- if (nativeIsSRGB(mNativePtr)) {
+ if (nativeIsConfigF16(mNativePtr)) {
+ // an F16 bitmaps is intended to always be linear extended, but due to
+ // inconsistencies in Bitmap.create() functions it is possible to have
+ // rendered into a bitmap in non-linear sRGB.
+ if (nativeIsSRGB(mNativePtr)) {
+ mColorSpace = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB);
+ } else {
+ mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
+ }
+ } else if (nativeIsSRGB(mNativePtr)) {
mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
- } else if (getConfig() == Config.HARDWARE && nativeIsSRGBLinear(mNativePtr)) {
- mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
+ } else if (nativeIsSRGBLinear(mNativePtr)) {
+ mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB);
} else {
float[] xyz = new float[9];
float[] params = new float[7];
@@ -2127,6 +2129,7 @@
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);
+ private static native boolean nativeIsConfigF16(long nativeBitmap);
private static native int nativeGetPixel(long nativeBitmap, int x, int y);
private static native void nativeGetPixels(long nativeBitmap, int[] pixels,
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 56b1885..4c67513 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -62,10 +62,8 @@
return displayInfo;
}
-static void queryWideColorGamutPreference(SkColorSpace::Gamut* colorGamut,
- sk_sp<SkColorSpace>* colorSpace, SkColorType* colorType) {
+static void queryWideColorGamutPreference(sk_sp<SkColorSpace>* colorSpace, SkColorType* colorType) {
if (Properties::isolatedProcess) {
- *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
*colorSpace = SkColorSpace::MakeSRGB();
*colorType = SkColorType::kN32_SkColorType;
return;
@@ -78,16 +76,13 @@
LOG_ALWAYS_FATAL_IF(status, "Failed to get composition preference, error %d", status);
switch (wcgDataspace) {
case ui::Dataspace::DISPLAY_P3:
- *colorGamut = SkColorSpace::Gamut::kDCIP3_D65_Gamut;
*colorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
break;
case ui::Dataspace::V0_SCRGB:
- *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
*colorSpace = SkColorSpace::MakeSRGB();
break;
case ui::Dataspace::V0_SRGB:
// when sRGB is returned, it means wide color gamut is not supported.
- *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
*colorSpace = SkColorSpace::MakeSRGB();
break;
default:
@@ -112,7 +107,7 @@
mMaxTextureSize = -1;
#endif
mDisplayInfo = QueryDisplayInfo();
- queryWideColorGamutPreference(&mWideColorGamut, &mWideColorSpace, &mWideColorType);
+ queryWideColorGamutPreference(&mWideColorSpace, &mWideColorType);
}
int DeviceInfo::maxTextureSize() const {
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index 9bcc8e8..2bab5d3 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -38,7 +38,6 @@
// context or if you are using the HWUI_NULL_GPU
int maxTextureSize() const;
const DisplayInfo& displayInfo() const { return mDisplayInfo; }
- SkColorSpace::Gamut getWideColorGamut() const { return mWideColorGamut; }
sk_sp<SkColorSpace> getWideColorSpace() const { return mWideColorSpace; }
SkColorType getWideColorType() const { return mWideColorType; }
@@ -50,7 +49,6 @@
int mMaxTextureSize;
DisplayInfo mDisplayInfo;
- SkColorSpace::Gamut mWideColorGamut;
sk_sp<SkColorSpace> mWideColorSpace;
SkColorType mWideColorType;
};
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 635d0ec..39bfcdd 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -164,15 +164,11 @@
const SkImageInfo& info = source.info();
bitmap.allocPixels(
SkImageInfo::MakeN32(info.width(), info.height(), info.alphaType(), nullptr));
- bitmap.eraseColor(0);
- if (info.colorType() == kRGBA_F16_SkColorType) {
- // Drawing RGBA_F16 onto ARGB_8888 is not supported
- source.readPixels(bitmap.info().makeColorSpace(SkColorSpace::MakeSRGB()),
- bitmap.getPixels(), bitmap.rowBytes(), 0, 0);
- } else {
- SkCanvas canvas(bitmap);
- canvas.drawBitmap(source, 0.0f, 0.0f, nullptr);
- }
+
+ SkCanvas canvas(bitmap);
+ canvas.drawColor(0);
+ canvas.drawBitmap(source, 0.0f, 0.0f, nullptr);
+
return bitmap;
}
}
@@ -253,8 +249,8 @@
eglDestroySyncKHR(display, fence);
}
- return Bitmap::createFrom(buffer.get(), bitmap.refColorSpace(), bitmap.alphaType(),
- Bitmap::computePalette(bitmap));
+ return Bitmap::createFrom(buffer.get(), bitmap.colorType(), bitmap.refColorSpace(),
+ bitmap.alphaType(), Bitmap::computePalette(bitmap));
}
void HardwareBitmapUploader::terminate() {
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 6e0258c..3bbee18 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -133,12 +133,11 @@
}
-sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, sk_sp<SkColorSpace> colorSpace,
- SkAlphaType alphaType, BitmapPalette palette) {
- // As we will be effectively texture-sampling the buffer (using either EGL or Vulkan), we can
- // view the format as RGBA8888.
+sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, SkColorType colorType,
+ sk_sp<SkColorSpace> colorSpace, SkAlphaType alphaType,
+ BitmapPalette palette) {
SkImageInfo info = SkImageInfo::Make(graphicBuffer->getWidth(), graphicBuffer->getHeight(),
- kRGBA_8888_SkColorType, alphaType, colorSpace);
+ colorType, alphaType, colorSpace);
return sk_sp<Bitmap>(new Bitmap(graphicBuffer.get(), info, palette));
}
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index 2138040..01e4516 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -72,6 +72,7 @@
* memory that is provided as an input param.
*/
static sk_sp<Bitmap> createFrom(sp<GraphicBuffer> graphicBuffer,
+ SkColorType colorType,
sk_sp<SkColorSpace> colorSpace,
SkAlphaType alphaType = kPremul_SkAlphaType,
BitmapPalette palette = BitmapPalette::Unknown);
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index cfbb995..570e895 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -167,7 +167,7 @@
if (surface) {
mRenderThread.requireGlContext();
- auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorGamut);
+ auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorSpace);
if (!newSurface) {
return false;
}
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 47c9094..a00a36f 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -476,11 +476,9 @@
void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
if (colorMode == ColorMode::SRGB) {
mSurfaceColorType = SkColorType::kN32_SkColorType;
- mSurfaceColorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
mSurfaceColorSpace = SkColorSpace::MakeSRGB();
} else if (colorMode == ColorMode::WideColorGamut) {
mSurfaceColorType = DeviceInfo::get()->getWideColorType();
- mSurfaceColorGamut = DeviceInfo::get()->getWideColorGamut();
mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace();
} else {
LOG_ALWAYS_FATAL("Unreachable: unsupported color mode.");
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index e9957df..7381e04 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -116,7 +116,6 @@
renderthread::RenderThread& mRenderThread;
SkColorType mSurfaceColorType;
- SkColorSpace::Gamut mSurfaceColorGamut;
sk_sp<SkColorSpace> mSurfaceColorSpace;
private:
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 53495a7..d0fe022 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -129,7 +129,7 @@
setSurfaceColorProperties(colorMode);
if (surface) {
mVkSurface = mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace,
- mSurfaceColorGamut, mSurfaceColorType);
+ mSurfaceColorType);
}
return mVkSurface != nullptr;
@@ -149,20 +149,8 @@
sk_sp<Bitmap> SkiaVulkanPipeline::allocateHardwareBitmap(renderthread::RenderThread& renderThread,
SkBitmap& skBitmap) {
- // TODO: implement this function for Vulkan pipeline
- // code below is a hack to avoid crashing because of missing HW Bitmap support
- sp<GraphicBuffer> buffer = new GraphicBuffer(
- skBitmap.info().width(), skBitmap.info().height(), PIXEL_FORMAT_RGBA_8888,
- GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER |
- GraphicBuffer::USAGE_SW_READ_NEVER,
- std::string("SkiaVulkanPipeline::allocateHardwareBitmap pid [") +
- std::to_string(getpid()) + "]");
- status_t error = buffer->initCheck();
- if (error < 0) {
- ALOGW("SkiaVulkanPipeline::allocateHardwareBitmap() failed in GraphicBuffer.create()");
- return nullptr;
- }
- return Bitmap::createFrom(buffer, skBitmap.refColorSpace());
+ LOG_ALWAYS_FATAL("Unimplemented");
+ return nullptr;
}
} /* namespace skiapipeline */
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 8cd97ed..2cc3f36 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -132,11 +132,13 @@
createPBufferSurface();
makeCurrent(mPBufferSurface, nullptr, /* force */ true);
- SkColorSpace::Gamut wideColorGamut = DeviceInfo::get()->getWideColorGamut();
+ skcms_Matrix3x3 wideColorGamut;
+ LOG_ALWAYS_FATAL_IF(!DeviceInfo::get()->getWideColorSpace()->toXYZD50(&wideColorGamut),
+ "Could not get gamut matrix from wideColorSpace");
bool hasWideColorSpaceExtension = false;
- if (wideColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) {
+ if (memcmp(&wideColorGamut, &SkNamedGamut::kDCIP3, sizeof(wideColorGamut)) == 0) {
hasWideColorSpaceExtension = EglExtensions.displayP3;
- } else if (wideColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) {
+ } else if (memcmp(&wideColorGamut, &SkNamedGamut::kSRGB, sizeof(wideColorGamut)) == 0) {
hasWideColorSpaceExtension = EglExtensions.scRGB;
} else {
LOG_ALWAYS_FATAL("Unsupported wide color space.");
@@ -297,7 +299,7 @@
Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
ColorMode colorMode,
- SkColorSpace::Gamut colorGamut) {
+ sk_sp<SkColorSpace> colorSpace) {
LOG_ALWAYS_FATAL_IF(!hasEglContext(), "Not initialized");
bool wideColorGamut = colorMode == ColorMode::WideColorGamut && mHasWideColorGamutSupport &&
@@ -330,15 +332,15 @@
if (EglExtensions.glColorSpace) {
attribs[0] = EGL_GL_COLORSPACE_KHR;
if (wideColorGamut) {
- switch (colorGamut) {
- case SkColorSpace::Gamut::kDCIP3_D65_Gamut:
- attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
- break;
- case SkColorSpace::Gamut::kSRGB_Gamut:
- attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
- break;
- default:
- LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
+ skcms_Matrix3x3 colorGamut;
+ LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut),
+ "Could not get gamut matrix from color space");
+ if (memcmp(&colorGamut, &SkNamedGamut::kDCIP3, sizeof(colorGamut)) == 0) {
+ attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
+ } else if (memcmp(&colorGamut, &SkNamedGamut::kSRGB, sizeof(colorGamut)) == 0) {
+ attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
+ } else {
+ LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
}
} else {
attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 4dd9096..27d41d2 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -49,7 +49,7 @@
bool hasEglContext();
Result<EGLSurface, EGLint> createSurface(EGLNativeWindowType window, ColorMode colorMode,
- SkColorSpace::Gamut colorGamut);
+ sk_sp<SkColorSpace> colorSpace);
void destroySurface(EGLSurface surface);
void destroy();
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index ab59af7..720c603 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -241,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) {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 5c6cb9a..1e75202 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -516,10 +516,9 @@
if (windowWidth != surface->mWindowWidth || windowHeight != surface->mWindowHeight) {
ColorMode colorMode = surface->mColorMode;
sk_sp<SkColorSpace> colorSpace = surface->mColorSpace;
- SkColorSpace::Gamut colorGamut = surface->mColorGamut;
SkColorType colorType = surface->mColorType;
destroySurface(surface);
- *surfaceOut = createSurface(window, colorMode, colorSpace, colorGamut, colorType);
+ *surfaceOut = createSurface(window, colorMode, colorSpace, colorType);
surface = *surfaceOut;
}
@@ -841,9 +840,12 @@
}
if (surface->mColorMode == ColorMode::WideColorGamut) {
- if (surface->mColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) {
+ skcms_Matrix3x3 surfaceGamut;
+ LOG_ALWAYS_FATAL_IF(!surface->mColorSpace->toXYZD50(&surfaceGamut),
+ "Could not get gamut matrix from color space");
+ if (memcmp(&surfaceGamut, &SkNamedGamut::kSRGB, sizeof(surfaceGamut)) == 0) {
colorSpace = VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT;
- } else if (surface->mColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) {
+ } else if (memcmp(&surfaceGamut, &SkNamedGamut::kDCIP3, sizeof(surfaceGamut)) == 0) {
colorSpace = VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT;
} else {
LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
@@ -922,7 +924,6 @@
VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode colorMode,
sk_sp<SkColorSpace> surfaceColorSpace,
- SkColorSpace::Gamut surfaceColorGamut,
SkColorType surfaceColorType) {
initialize();
@@ -931,7 +932,7 @@
}
VulkanSurface* surface = new VulkanSurface(colorMode, window, surfaceColorSpace,
- surfaceColorGamut, surfaceColorType);
+ surfaceColorType);
VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo;
memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR));
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index b06eb82..abe78ef 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -39,9 +39,9 @@
class VulkanSurface {
public:
VulkanSurface(ColorMode colorMode, ANativeWindow* window, sk_sp<SkColorSpace> colorSpace,
- SkColorSpace::Gamut colorGamut, SkColorType colorType)
+ SkColorType colorType)
: mColorMode(colorMode), mNativeWindow(window), mColorSpace(colorSpace),
- mColorGamut(colorGamut), mColorType(colorType) {}
+ mColorType(colorType) {}
sk_sp<SkSurface> getBackBufferSurface() { return mBackbuffer; }
@@ -90,7 +90,6 @@
int mWindowWidth = 0;
int mWindowHeight = 0;
sk_sp<SkColorSpace> mColorSpace;
- SkColorSpace::Gamut mColorGamut;
SkColorType mColorType;
VkSurfaceTransformFlagBitsKHR mTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
SkMatrix mPreTransform;
@@ -113,7 +112,6 @@
// VulkanSurface object which is returned.
VulkanSurface* createSurface(ANativeWindow* window, ColorMode colorMode,
sk_sp<SkColorSpace> surfaceColorSpace,
- SkColorSpace::Gamut surfaceColorGamut,
SkColorType surfaceColorType);
// Destroy the VulkanSurface and all associated vulkan objects.
diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
index ec81f62..2af955f 100644
--- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
+++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
@@ -50,7 +50,8 @@
pixels[4000 + 4 * i + 3] = 255;
}
buffer->unlock();
- sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer, SkColorSpace::MakeSRGB()));
+ sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer, kRGBA_8888_SkColorType,
+ SkColorSpace::MakeSRGB()));
sk_sp<SkShader> hardwareShader(createBitmapShader(*hardwareBitmap));
SkPoint center;
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index 4415a59..d14116f 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -45,6 +45,20 @@
}
}
+SkColorType PixelFormatToColorType(android::PixelFormat format) {
+ switch (format) {
+ case PIXEL_FORMAT_RGBX_8888: return kRGB_888x_SkColorType;
+ case PIXEL_FORMAT_RGBA_8888: return kRGBA_8888_SkColorType;
+ case PIXEL_FORMAT_RGBA_FP16: return kRGBA_F16_SkColorType;
+ case PIXEL_FORMAT_RGB_565: return kRGB_565_SkColorType;
+ case PIXEL_FORMAT_RGBA_1010102: return kRGBA_1010102_SkColorType;
+ case PIXEL_FORMAT_RGBA_4444: return kARGB_4444_SkColorType;
+ default:
+ ALOGW("Unsupported PixelFormat: %d, return kUnknown_SkColorType by default", format);
+ return kUnknown_SkColorType;
+ }
+}
+
sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) {
skcms_Matrix3x3 gamut;
diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h
index 3880252..b67d10d 100644
--- a/libs/hwui/utils/Color.h
+++ b/libs/hwui/utils/Color.h
@@ -112,6 +112,7 @@
}
android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType);
+ANDROID_API SkColorType PixelFormatToColorType(android::PixelFormat format);
ANDROID_API sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace);
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 100%
rename from media/java/android/media/MediaController2.java
rename to media/apex/java/android/media/MediaController2.java
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 100%
rename from media/java/android/media/MediaSession2.java
rename to media/apex/java/android/media/MediaSession2.java
diff --git a/media/java/android/media/MediaSession2Service.java b/media/apex/java/android/media/MediaSession2Service.java
similarity index 100%
rename from media/java/android/media/MediaSession2Service.java
rename to media/apex/java/android/media/MediaSession2Service.java
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 100%
rename from media/java/android/media/Session2Token.java
rename to media/apex/java/android/media/Session2Token.java
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 97%
rename from media/java/android/media/session/ControllerCallbackLink.java
rename to media/apex/java/android/media/session/ControllerCallbackLink.java
index 2d59e44..adc14a5 100644
--- a/media/java/android/media/session/ControllerCallbackLink.java
+++ b/media/apex/java/android/media/session/ControllerCallbackLink.java
@@ -315,12 +315,6 @@
}
private void ensureMediaControlPermission() {
- // Allow API calls from the System UI
- if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
- == PackageManager.PERMISSION_GRANTED) {
- return;
- }
-
// 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 100%
rename from media/java/android/media/session/ISessionControllerCallback.aidl
rename to media/apex/java/android/media/session/ISessionControllerCallback.aidl
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 99%
rename from media/java/android/media/session/MediaController.java
rename to media/apex/java/android/media/session/MediaController.java
index 057c9cb..d43acf4 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/apex/java/android/media/session/MediaController.java
@@ -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/java/android/media/session/MediaSessionEngine.java b/media/apex/java/android/media/session/MediaSessionEngine.java
similarity index 99%
rename from media/java/android/media/session/MediaSessionEngine.java
rename to media/apex/java/android/media/session/MediaSessionEngine.java
index f159a95..1f5fa5f 100644
--- a/media/java/android/media/session/MediaSessionEngine.java
+++ b/media/apex/java/android/media/session/MediaSessionEngine.java
@@ -53,7 +53,7 @@
*/
@SystemApi
public final class MediaSessionEngine implements AutoCloseable {
- private static final String TAG = MediaSession.TAG;
+ private static final String TAG = "MediaSession";
private final Object mLock = new Object();
private final int mMaxBitmapSize;
@@ -172,7 +172,7 @@
*
* @param flags The flags to set for this session.
*/
- public void setFlags(@MediaSession.SessionFlags int flags) {
+ public void setFlags(int flags) {
try {
mSessionLink.setFlags(flags);
} catch (RuntimeException e) {
@@ -409,7 +409,7 @@
* <li>{@link Rating#RATING_THUMB_UP_DOWN}</li>
* </ul>
*/
- public void setRatingType(@Rating.Style int type) {
+ public void setRatingType(int type) {
try {
mSessionLink.setRatingType(type);
} catch (RuntimeException e) {
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 95%
rename from media/java/android/media/session/PlaybackState.java
rename to media/apex/java/android/media/session/PlaybackState.java
index 0d0ec4c..6b28c97 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/apex/java/android/media/session/PlaybackState.java
@@ -41,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,
@@ -191,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
@@ -234,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
@@ -242,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
@@ -252,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
@@ -274,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;
@@ -534,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}.
@@ -588,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 99%
rename from media/java/android/media/session/SessionCallbackLink.java
rename to media/apex/java/android/media/session/SessionCallbackLink.java
index 0dbf427..3bcb65c 100644
--- a/media/java/android/media/session/SessionCallbackLink.java
+++ b/media/apex/java/android/media/session/SessionCallbackLink.java
@@ -944,12 +944,6 @@
}
private void ensureMediaControlPermission() {
- // Allow API calls from the System UI
- if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
- == PackageManager.PERMISSION_GRANTED) {
- return;
- }
-
// 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 2fbc699..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,
@@ -607,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;
}
@@ -648,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();
@@ -819,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.
@@ -829,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/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 4896d08..1a185e9 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -481,7 +481,7 @@
* @hide
*/
@SystemApi
- ControllerLink getControllerLink() {
+ public ControllerLink getControllerLink() {
return mControllerLink;
}
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/libandroid.map.txt b/native/android/libandroid.map.txt
index 8b45af0..51afbc7 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -231,6 +231,7 @@
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
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index f0100a9..5fae9d5 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -44,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);
}
@@ -431,3 +501,24 @@
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/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp
index 9b6ad38..9064ebe 100644
--- a/packages/CarSystemUI/Android.bp
+++ b/packages/CarSystemUI/Android.bp
@@ -81,5 +81,5 @@
"com.android.keyguard",
],
- annotation_processors: ["dagger2-compiler-2.19"],
+ plugins: ["dagger2-compiler-2.19"],
}
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index 052566d..e591ea9 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -65,8 +65,9 @@
<com.android.systemui.statusbar.car.CarFacetButton
android:id="@+id/music_nav"
style="@style/NavigationBarButton"
+ systemui:categories="android.intent.category.APP_MUSIC"
systemui:icon="@drawable/car_ic_music"
- systemui:intent="intent:#Intent;component=com.android.car.media/.MediaActivity;launchFlags=0x14000000;end"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.MEDIA_TEMPLATE;launchFlags=0x10000000;end"
systemui:packages="com.android.car.media"
systemui:selectedIcon="@drawable/car_ic_music_selected"
systemui:useMoreIcon="false"
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/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/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index 7357fe6..42afb69 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -16,6 +16,7 @@
package com.android.settingslib.applications;
+import android.app.Application;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentFilter;
@@ -123,4 +124,12 @@
return null;
}
+ /**
+ * Returns a boolean indicating whether the given package is a hidden system module
+ */
+ public static boolean isHiddenSystemModule(Context context, String packageName) {
+ return ApplicationsState.getInstance((Application) context.getApplicationContext())
+ .isHiddenModule(packageName);
+ }
+
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index a936df2..c9fbc7b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -29,6 +29,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageStatsObserver;
+import android.content.pm.ModuleInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageStats;
@@ -71,6 +72,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@@ -95,9 +97,14 @@
static ApplicationsState sInstance;
public static ApplicationsState getInstance(Application app) {
+ return getInstance(app, AppGlobals.getPackageManager());
+ }
+
+ @VisibleForTesting
+ static ApplicationsState getInstance(Application app, IPackageManager iPackageManager) {
synchronized (sLock) {
if (sInstance == null) {
- sInstance = new ApplicationsState(app);
+ sInstance = new ApplicationsState(app, iPackageManager);
}
return sInstance;
}
@@ -132,6 +139,7 @@
String mCurComputingSizePkg;
int mCurComputingSizeUserId;
boolean mSessionsChanged;
+ final HashSet<String> mHiddenModules = new HashSet<>();
// Temporary for dispatching session callbacks. Only touched by main thread.
final ArrayList<WeakReference<Session>> mActiveSessions = new ArrayList<>();
@@ -172,11 +180,11 @@
FLAG_SESSION_REQUEST_HOME_APP | FLAG_SESSION_REQUEST_ICONS |
FLAG_SESSION_REQUEST_SIZES | FLAG_SESSION_REQUEST_LAUNCHER;
- private ApplicationsState(Application app) {
+ private ApplicationsState(Application app, IPackageManager iPackageManager) {
mContext = app;
mPm = mContext.getPackageManager();
mDrawableFactory = IconDrawableFactory.newInstance(mContext);
- mIpm = AppGlobals.getPackageManager();
+ mIpm = iPackageManager;
mUm = mContext.getSystemService(UserManager.class);
mStats = mContext.getSystemService(StorageStatsManager.class);
for (int userId : mUm.getProfileIdsWithDisabled(UserHandle.myUserId())) {
@@ -194,6 +202,13 @@
mRetrieveFlags = PackageManager.MATCH_DISABLED_COMPONENTS |
PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+ final List<ModuleInfo> moduleInfos = mPm.getInstalledModules(0 /* flags */);
+ for (ModuleInfo info : moduleInfos) {
+ if (info.isHidden()) {
+ mHiddenModules.add(info.getPackageName());
+ }
+ }
+
/**
* This is a trick to prevent the foreground thread from being delayed.
* The problem is that Dalvik monitors are initially spin locks, to keep
@@ -283,6 +298,10 @@
}
mHaveDisabledApps = true;
}
+ if (isHiddenModule(info.packageName)) {
+ mApplications.remove(i--);
+ continue;
+ }
if (!mHaveInstantApps && AppUtils.isInstant(info)) {
mHaveInstantApps = true;
}
@@ -314,10 +333,15 @@
public boolean haveDisabledApps() {
return mHaveDisabledApps;
}
+
public boolean haveInstantApps() {
return mHaveInstantApps;
}
+ boolean isHiddenModule(String packageName) {
+ return mHiddenModules.contains(packageName);
+ }
+
void doPauseIfNeededLocked() {
if (!mResumed) {
return;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index ccec175a..a098ecc 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -18,7 +18,9 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.shadow.api.Shadow.extract;
@@ -33,12 +35,16 @@
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.ModuleInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.IconDrawableFactory;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
@@ -46,11 +52,11 @@
import com.android.settingslib.applications.ApplicationsState.Session;
import com.android.settingslib.testutils.shadow.ShadowUserManager;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatchers;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -78,6 +84,8 @@
/** Class under test */
private ApplicationsState mApplicationsState;
+ private Session mSession;
+
@Mock
private Callbacks mCallbacks;
@@ -85,6 +93,8 @@
private ArgumentCaptor<ArrayList<AppEntry>> mAppEntriesCaptor;
@Mock
private StorageStatsManager mStorageStatsManager;
+ @Mock
+ private IPackageManager mPackageManagerService;
@Implements(value = IconDrawableFactory.class)
public static class ShadowIconDrawableFactory {
@@ -99,6 +109,11 @@
public static class ShadowPackageManager extends
org.robolectric.shadows.ShadowApplicationPackageManager {
+ // test installed modules, 2 regular, 2 hidden
+ private final String[] mModuleNames = {
+ "test.module.1", "test.hidden.module.2", "test.hidden.module.3", "test.module.4"};
+ private final List<ModuleInfo> mInstalledModules = new ArrayList<>();
+
@Implementation
protected ComponentName getHomeActivities(List<ResolveInfo> outActivities) {
ResolveInfo resolveInfo = new ResolveInfo();
@@ -109,6 +124,16 @@
return ComponentName.createRelative(resolveInfo.activityInfo.packageName, "foo");
}
+ @Implementation
+ public List<ModuleInfo> getInstalledModules(int flags) {
+ if (mInstalledModules.isEmpty()) {
+ for (String moduleName : mModuleNames) {
+ mInstalledModules.add(createModuleInfo(moduleName));
+ }
+ }
+ return mInstalledModules;
+ }
+
public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
@PackageManager.ResolveInfoFlags int flags, @UserIdInt int userId) {
List<ResolveInfo> resolveInfos = new ArrayList<>();
@@ -121,6 +146,15 @@
resolveInfos.add(resolveInfo);
return resolveInfos;
}
+
+ private ModuleInfo createModuleInfo(String packageName) {
+ final ModuleInfo info = new ModuleInfo();
+ info.setName(packageName);
+ info.setPackageName(packageName);
+ // will treat any app with package name that contains "hidden" as hidden module
+ info.setHidden(!TextUtils.isEmpty(packageName) && packageName.contains("hidden"));
+ return info;
+ }
}
@Before
@@ -136,12 +170,28 @@
storageStats.codeBytes = 10;
storageStats.dataBytes = 20;
storageStats.cacheBytes = 30;
- when(mStorageStatsManager.queryStatsForPackage(ArgumentMatchers.any(UUID.class),
- anyString(), ArgumentMatchers.any(UserHandle.class))).thenReturn(storageStats);
+ when(mStorageStatsManager.queryStatsForPackage(any(UUID.class),
+ anyString(), any(UserHandle.class))).thenReturn(storageStats);
+
+ // Set up 3 installed apps, in which 1 is hidden module
+ final List<ApplicationInfo> infos = new ArrayList<>();
+ infos.add(createApplicationInfo("test.package.1"));
+ infos.add(createApplicationInfo("test.hidden.module.2"));
+ infos.add(createApplicationInfo("test.package.3"));
+ when(mPackageManagerService.getInstalledApplications(
+ anyInt() /* flags */, anyInt() /* userId */)).thenReturn(new ParceledListSlice(infos));
ApplicationsState.sInstance = null;
- mApplicationsState = ApplicationsState.getInstance(RuntimeEnvironment.application);
+ mApplicationsState =
+ ApplicationsState.getInstance(RuntimeEnvironment.application, mPackageManagerService);
mApplicationsState.clearEntries();
+
+ mSession = mApplicationsState.newSession(mCallbacks);
+ }
+
+ @After
+ public void tearDown() {
+ mSession.onDestroy();
}
private ApplicationInfo createApplicationInfo(String packageName) {
@@ -187,12 +237,11 @@
@Test
public void testDefaultSessionLoadsAll() {
- Session session = mApplicationsState.newSession(mCallbacks);
- session.onResume();
+ mSession.onResume();
addApp(HOME_PACKAGE_NAME, 1);
addApp(LAUNCHABLE_PACKAGE_NAME, 2);
- session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+ mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
@@ -211,17 +260,15 @@
AppEntry launchableEntry = findAppEntry(appEntries, 2);
assertThat(launchableEntry.hasLauncherEntry).isTrue();
assertThat(launchableEntry.launcherEntryEnabled).isTrue();
- session.onDestroy();
}
@Test
public void testCustomSessionLoadsIconsOnly() {
- Session session = mApplicationsState.newSession(mCallbacks);
- session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS);
- session.onResume();
+ mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS);
+ mSession.onResume();
addApp(LAUNCHABLE_PACKAGE_NAME, 1);
- session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+ mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
@@ -232,17 +279,15 @@
assertThat(launchableEntry.icon).isNotNull();
assertThat(launchableEntry.size).isEqualTo(-1);
assertThat(launchableEntry.hasLauncherEntry).isFalse();
- session.onDestroy();
}
@Test
public void testCustomSessionLoadsSizesOnly() {
- Session session = mApplicationsState.newSession(mCallbacks);
- session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_SIZES);
- session.onResume();
+ mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_SIZES);
+ mSession.onResume();
addApp(LAUNCHABLE_PACKAGE_NAME, 1);
- session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+ mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
@@ -253,17 +298,15 @@
assertThat(launchableEntry.icon).isNull();
assertThat(launchableEntry.hasLauncherEntry).isFalse();
assertThat(launchableEntry.size).isGreaterThan(0L);
- session.onDestroy();
}
@Test
public void testCustomSessionLoadsHomeOnly() {
- Session session = mApplicationsState.newSession(mCallbacks);
- session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_HOME_APP);
- session.onResume();
+ mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_HOME_APP);
+ mSession.onResume();
addApp(HOME_PACKAGE_NAME, 1);
- session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+ mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
@@ -275,17 +318,15 @@
assertThat(launchableEntry.hasLauncherEntry).isFalse();
assertThat(launchableEntry.size).isEqualTo(-1);
assertThat(launchableEntry.isHomeApp).isTrue();
- session.onDestroy();
}
@Test
public void testCustomSessionLoadsLeanbackOnly() {
- Session session = mApplicationsState.newSession(mCallbacks);
- session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER);
- session.onResume();
+ mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER);
+ mSession.onResume();
addApp(LAUNCHABLE_PACKAGE_NAME, 1);
- session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+ mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
@@ -298,6 +339,16 @@
assertThat(launchableEntry.isHomeApp).isFalse();
assertThat(launchableEntry.hasLauncherEntry).isTrue();
assertThat(launchableEntry.launcherEntryEnabled).isTrue();
- session.onDestroy();
}
+
+ @Test
+ public void onResume_shouldNotIncludeSystemHiddenModule() {
+ mSession.onResume();
+
+ final List<ApplicationInfo> mApplications = mApplicationsState.mApplications;
+ assertThat(mApplications).hasSize(2);
+ assertThat(mApplications.get(0).packageName).isEqualTo("test.package.1");
+ assertThat(mApplications.get(1).packageName).isEqualTo("test.package.3");
+ }
+
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index e843eb4..aff6f04 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,
@@ -699,6 +698,9 @@
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES,
GlobalSettingsProto.Gpu.ANGLE_GL_DRIVER_SELECTION_VALUES);
dumpSetting(s, p,
+ Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST,
+ GlobalSettingsProto.Gpu.ANGLE_WHITELIST);
+ dumpSetting(s, p,
Settings.Global.GPU_DEBUG_LAYER_APP,
GlobalSettingsProto.Gpu.DEBUG_LAYER_APP);
dumpSetting(s, p,
@@ -716,6 +718,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 +742,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 +837,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 +896,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 +1123,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 +1273,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 +1572,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 +2388,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/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 8be67d9..0a62b7c 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -73,7 +73,7 @@
"com.android.keyguard",
],
- annotation_processors: ["dagger2-compiler-2.19"],
+ plugins: ["dagger2-compiler-2.19"],
}
android_library {
@@ -127,7 +127,7 @@
"--extra-packages",
"com.android.keyguard:com.android.systemui",
],
- annotation_processors: ["dagger2-compiler-2.19"],
+ plugins: ["dagger2-compiler-2.19"],
}
android_app {
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/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
index 0b1dab1..fc84332 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
@@ -20,14 +20,14 @@
import android.view.View;
import android.view.ViewGroup;
-import java.util.ArrayList;
-
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.annotations.DependsOn;
import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+
+import java.util.ArrayList;
@ProvidesInterface(action = NotificationMenuRowPlugin.ACTION,
version = NotificationMenuRowPlugin.VERSION)
@@ -149,6 +149,12 @@
public boolean canBeDismissed();
/**
+ * Informs the menu whether dismiss gestures are left-to-right or right-to-left.
+ */
+ default void setDismissRtl(boolean dismissRtl) {
+ }
+
+ /**
* Determines whether the menu should remain open given its current state, or snap closed.
* @return true if the menu should remain open, false otherwise.
*/
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 13d27bb..190bd7a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -159,6 +159,12 @@
<!-- 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>
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/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 7b6e79b..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));
}
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/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
new file mode 100644
index 0000000..4944732
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.statusbar
+
+import android.annotation.ColorInt
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Point
+import android.graphics.Rect
+import android.renderscript.Allocation
+import android.renderscript.Element
+import android.renderscript.RenderScript
+import android.renderscript.ScriptIntrinsicBlur
+import android.util.MathUtils
+import com.android.internal.graphics.ColorUtils
+
+import javax.inject.Inject
+import javax.inject.Singleton
+
+private const val COLOR_ALPHA = (255 * 0.7f).toInt()
+private const val BLUR_RADIUS = 25f
+private const val DOWNSAMPLE = 6
+
+@Singleton
+class MediaArtworkProcessor @Inject constructor() {
+
+ private val mTmpSize = Point()
+ private var mArtworkCache: Bitmap? = null
+
+ fun processArtwork(context: Context, artwork: Bitmap, @ColorInt color: Int): Bitmap {
+ if (mArtworkCache != null) {
+ return mArtworkCache!!
+ }
+
+ context.display.getSize(mTmpSize)
+ val renderScript = RenderScript.create(context)
+ val rect = Rect(0, 0,artwork.width, artwork.height)
+ MathUtils.fitRect(rect, Math.max(mTmpSize.x / DOWNSAMPLE, mTmpSize.y / DOWNSAMPLE))
+ val inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(),
+ true /* filter */)
+ val input = Allocation.createFromBitmap(renderScript, inBitmap,
+ Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE)
+ val outBitmap = Bitmap.createBitmap(inBitmap.width, inBitmap.height,
+ Bitmap.Config.ARGB_8888)
+ val output = Allocation.createFromBitmap(renderScript, outBitmap)
+ val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript))
+ blur.setRadius(BLUR_RADIUS)
+ blur.setInput(input)
+ blur.forEach(output)
+ output.copyTo(outBitmap)
+
+ input.destroy()
+ output.destroy()
+ inBitmap.recycle()
+
+ val canvas = Canvas(outBitmap)
+ canvas.drawColor(ColorUtils.setAlphaComponent(color, COLOR_ALPHA))
+ return outBitmap
+ }
+
+ fun clearCache() {
+ mArtworkCache?.recycle()
+ mArtworkCache = null
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index f46ded4d..c25b7cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -39,6 +39,9 @@
boolean isCurrentProfile(int userId);
+ /** Adds a listener to be notified when the current user changes. */
+ void addUserChangedListener(UserChangedListener listener);
+
void destroy();
SparseArray<UserInfo> getCurrentProfiles();
@@ -58,4 +61,9 @@
boolean needsRedaction(NotificationEntry entry);
boolean userAllowsPrivateNotificationsInPublic(int currentUserId);
+
+ /** Notified when the current user changes. */
+ interface UserChangedListener {
+ void onUserChanged(int userId);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index d5f4d04..4f9d428 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -49,11 +49,14 @@
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;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
/**
* Handles keeping track of the current user, profiles, and various things related to hiding
@@ -77,6 +80,7 @@
private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
private final UserManager mUserManager;
private final IStatusBarService mBarService;
+ private final List<UserChangedListener> mListeners = new ArrayList<>();
private boolean mShowLockscreenNotifications;
private boolean mAllowLockscreenRemoteInput;
@@ -111,6 +115,10 @@
updatePublicMode();
mPresenter.onUserSwitched(mCurrentUserId);
getEntryManager().getNotificationData().filterAndSort();
+
+ for (UserChangedListener listener : mListeners) {
+ listener.onUserChanged(mCurrentUserId);
+ }
} else if (Intent.ACTION_USER_ADDED.equals(action)) {
updateCurrentProfilesCache();
} else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
@@ -130,8 +138,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) {
@@ -498,6 +509,10 @@
}
}
+ @Override
+ public void addUserChangedListener(UserChangedListener listener) {
+ mListeners.add(listener);
+ }
// public void updatePublicMode() {
// //TODO: I think there may be a race condition where mKeyguardViewManager.isShowing() returns
@@ -537,6 +552,7 @@
public void destroy() {
mContext.unregisterReceiver(mBaseBroadcastReceiver);
mContext.unregisterReceiver(mAllUsersReceiver);
+ mListeners.clear();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 7412702..98a3a54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -25,6 +25,7 @@
import android.app.Notification;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -102,6 +103,7 @@
private final Context mContext;
private final MediaSessionManager mMediaSessionManager;
private final ArrayList<MediaListener> mMediaListeners;
+ private final MediaArtworkProcessor mMediaArtworkProcessor;
protected NotificationPresenter mPresenter;
private MediaController mMediaController;
@@ -133,6 +135,7 @@
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
}
+ mMediaArtworkProcessor.clearCache();
mMediaMetadata = metadata;
dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */);
}
@@ -143,8 +146,10 @@
Context context,
Lazy<ShadeController> shadeController,
Lazy<StatusBarWindowController> statusBarWindowController,
- NotificationEntryManager notificationEntryManager) {
+ NotificationEntryManager notificationEntryManager,
+ MediaArtworkProcessor mediaArtworkProcessor) {
mContext = context;
+ mMediaArtworkProcessor = mediaArtworkProcessor;
mMediaListeners = new ArrayList<>();
mMediaSessionManager
= (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
@@ -366,6 +371,7 @@
}
private void clearCurrentMediaNotificationSession() {
+ mMediaArtworkProcessor.clearCache();
mMediaMetadata = null;
if (mMediaController != null) {
if (DEBUG_MEDIA) {
@@ -418,7 +424,19 @@
// might still be null
}
if (artworkBitmap != null) {
- artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), artworkBitmap);
+ int notificationColor;
+ synchronized (mEntryManager.getNotificationData()) {
+ NotificationEntry entry = mEntryManager.getNotificationData()
+ .get(mMediaNotificationKey);
+ if (entry == null || entry.getRow() == null) {
+ notificationColor = Color.TRANSPARENT;
+ } else {
+ notificationColor = entry.getRow().calculateBgColor();
+ }
+ }
+ Bitmap bmp = mMediaArtworkProcessor.processArtwork(mContext, artworkBitmap,
+ notificationColor);
+ artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp);
}
}
boolean allowWhenShade = false;
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/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 76d394d..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
@@ -32,7 +32,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.StatusBarStateController;
@@ -40,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;
@@ -128,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.
@@ -160,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,
@@ -363,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);
}
/**
@@ -397,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);
}
@@ -416,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) {
@@ -460,10 +501,8 @@
final State stateToBeLogged = new State(state);
mUiOffloadThread.submit(() -> {
try {
- mBarService.onNotificationExpansionChanged(
- key, stateToBeLogged.mIsUserAction, stateToBeLogged.mIsExpanded,
- // TODO (b/120767764): fill in location
- ExpandableViewState.LOCATION_UNKNOWN /* notificationLocation */);
+ mBarService.onNotificationExpansionChanged(key, stateToBeLogged.mIsUserAction,
+ stateToBeLogged.mIsExpanded, stateToBeLogged.mLocation.ordinal());
} catch (RemoteException e) {
Log.e(TAG, "Failed to call onNotificationExpansionChanged: ", e);
}
@@ -477,6 +516,8 @@
Boolean mIsExpanded;
@Nullable
Boolean mIsVisible;
+ @Nullable
+ NotificationVisibility.NotificationLocation mLocation;
private State() {}
@@ -484,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/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 296c061..bed2426 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -3092,6 +3092,11 @@
}
}
+ /** Sets whether dismiss gestures are right-to-left (instead of left-to-right). */
+ public void setDismissRtl(boolean dismissRtl) {
+ mMenuRow.setDismissRtl(dismissRtl);
+ }
+
private static class NotificationViewState extends ExpandableViewState {
@Override
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/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index d97162c..eafaba8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -78,6 +78,8 @@
private ArrayList<MenuItem> mRightMenuItems;
private final Map<View, MenuItem> mMenuItemsByView = new ArrayMap<>();
private OnMenuEventListener mMenuListener;
+ private boolean mDismissRtl;
+ private boolean mIsForeground;
private ValueAnimator mFadeAnimator;
private boolean mAnimating;
@@ -238,6 +240,8 @@
}
private void createMenuViews(boolean resetState, final boolean isForeground) {
+ mIsForeground = isForeground;
+
final Resources res = mContext.getResources();
mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
@@ -268,10 +272,11 @@
mRightMenuItems.add(mAppOpsItem);
mLeftMenuItems.addAll(mRightMenuItems);
} else {
- mRightMenuItems.add(mInfoItem);
- mRightMenuItems.add(mAppOpsItem);
+ ArrayList<MenuItem> menuItems = mDismissRtl ? mLeftMenuItems : mRightMenuItems;
+ menuItems.add(mInfoItem);
+ menuItems.add(mAppOpsItem);
if (!isForeground) {
- mRightMenuItems.add(mSnoozeItem);
+ menuItems.add(mSnoozeItem);
}
}
@@ -729,6 +734,14 @@
return getParent().canViewBeDismissed();
}
+ @Override
+ public void setDismissRtl(boolean dismissRtl) {
+ mDismissRtl = dismissRtl;
+ if (mMenuContainer != null) {
+ createMenuViews(true, mIsForeground);
+ }
+ }
+
public static class NotificationMenuItem implements MenuItem {
View mMenuView;
GutsContent mGutsContent;
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 2129b81..8d57ea5 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
@@ -174,6 +174,7 @@
private final boolean mShouldDrawNotificationBackground;
private boolean mLowPriorityBeforeSpeedBump;
private final boolean mAllowLongPress;
+ private boolean mDismissRtl;
private float mExpandedHeight;
private int mOwnScrollY;
@@ -533,8 +534,10 @@
tunerService.addTunable((key, newValue) -> {
if (key.equals(LOW_PRIORITY)) {
mLowPriorityBeforeSpeedBump = "1".equals(newValue);
+ } else if (key.equals(Settings.Secure.NOTIFICATION_DISMISS_RTL)) {
+ updateDismissRtlSetting("1".equals(newValue));
}
- }, LOW_PRIORITY);
+ }, LOW_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL);
mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
@@ -548,6 +551,16 @@
});
}
+ private void updateDismissRtlSetting(boolean dismissRtl) {
+ mDismissRtl = dismissRtl;
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) child).setDismissRtl(dismissRtl);
+ }
+ }
+ }
+
@Override
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
protected void onFinishInflate() {
@@ -3255,6 +3268,9 @@
generateAddAnimation(child, false /* fromMoreCard */);
updateAnimationState(child);
updateChronometerForChild(child);
+ if (child instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) child).setDismissRtl(mDismissRtl);
+ }
if (ANCHOR_SCROLLING) {
// TODO: once we're recycling this will need to check the adapter position of the child
if (child == getFirstChildNotGone() && (isScrolledToTop() || !mIsExpanded)) {
@@ -6315,7 +6331,7 @@
public boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) {
boolean isValidDirection;
if (NotificationUtils.useNewInterruptionModel(mContext)) {
- isValidDirection = isLayoutRtl() ? !isRightOrDown : isRightOrDown;
+ isValidDirection = mDismissRtl ? !isRightOrDown : isRightOrDown;
} else {
isValidDirection = true;
}
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/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/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/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
index 2f6b221..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
@@ -28,7 +28,6 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.UiOffloadThread;
@@ -73,7 +72,8 @@
@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(
@@ -82,7 +82,8 @@
@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());
@@ -94,26 +95,33 @@
@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, ExpandableViewState.LOCATION_UNKNOWN);
+ 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, ExpandableViewState.LOCATION_UNKNOWN);
+ NOTIFICATION_KEY, false, true,
+ // The last location seen should be logged (the one passed to onVisibilityChanged).
+ NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.toMetricsEventEnum()
+ );
}
@Test
@@ -121,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, ExpandableViewState.LOCATION_UNKNOWN);
+ NOTIFICATION_KEY, false, true,
+ // The last location seen should be logged (the one passed to onExpansionChanged).
+ NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP.toMetricsEventEnum());
}
@Test
@@ -133,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, ExpandableViewState.LOCATION_UNKNOWN);
+ 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/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index efa4e79..d1aa84f 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -6862,6 +6862,14 @@
// 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 6ff2b35..3a89316 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -220,6 +220,12 @@
// Package: android
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
// Legacy IDs existed as stable non-conflicting constants prior to the O release
@@ -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 79b63bc..c063e82 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -491,6 +491,9 @@
// 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.
@@ -962,6 +965,10 @@
// NetworkAgent Wifi usability score of connected wifi
optional int32 last_wifi_usability_score = 15 [default = -1];
+
+ // Prediction horizon (in second) of Wifi usability score provided by external
+ // system app
+ optional int32 last_prediction_horizon_sec = 16 [default = -1];
}
// Wi-Fi Aware metrics
@@ -1679,6 +1686,10 @@
// NetworkAgent wifi usability score of connected wifi.
// Defaults to -1 if the score was never set.
optional int32 last_wifi_usability_score = 11 [default = -1];
+
+ // Prediction horizon (in second) of Wifi usability score provided by external
+ // system app
+ optional int32 last_prediction_horizon_sec = 12 [default = -1];
}
message PasspointProfileTypeCount {
@@ -1800,6 +1811,21 @@
// 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;
+
+ // Prediction horizon (in second) of Wifi usability score provided by external
+ // system app
+ optional int32 prediction_horizon_sec = 23;
}
message WifiUsabilityStats {
@@ -1849,3 +1875,135 @@
// 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/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 dbe86c1..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;
@@ -208,12 +209,12 @@
private final WindowManagerInternal mWindowManagerService;
- private final DisplayManager mDisplayManager;
-
private AppWidgetManagerInternal mAppWidgetService;
private final SecurityPolicy mSecurityPolicy;
+ private final AccessibilityDisplayListener mA11yDisplayListener;
+
private final AppOpsManager mAppOpsManager;
private final MainHandler mMainHandler;
@@ -306,12 +307,11 @@
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mMainHandler = new MainHandler(mContext.getMainLooper());
mGlobalActionPerformer = new GlobalActionPerformer(mContext, mWindowManagerService);
- mDisplayManager = mContext.getSystemService(DisplayManager.class);
+ mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
registerBroadcastReceivers();
new AccessibilityContentObserver(mMainHandler).register(
context.getContentResolver());
- registerDisplayListener(mMainHandler);
}
@Override
@@ -527,30 +527,6 @@
}, UserHandle.ALL, intentFilter, null, null);
}
- private void registerDisplayListener(Handler handler) {
- mDisplayManager.registerDisplayListener(new DisplayManager.DisplayListener() {
- @Override
- public void onDisplayAdded(int displayId) {
- synchronized (mLock) {
- UserState userState = getCurrentUserStateLocked();
- updateMagnificationLocked(userState);
- }
- }
-
- @Override
- public void onDisplayRemoved(int displayId) {
- if (mMagnificationController != null) {
- mMagnificationController.onDisplayRemoved(displayId);
- }
- }
-
- @Override
- public void onDisplayChanged(int displayId) {
- // do nothing
- }
- }, handler);
- }
-
@Override
public long addClient(IAccessibilityManagerClient callback, int userId) {
synchronized (mLock) {
@@ -937,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);
}
}
@@ -1249,7 +1227,7 @@
}
}
- private void notifyAccessibilityButtonClickedLocked() {
+ private void notifyAccessibilityButtonClickedLocked(int displayId) {
final UserState state = getCurrentUserStateLocked();
int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0;
@@ -1266,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;
}
@@ -1281,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;
}
@@ -1299,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) {
@@ -2226,32 +2213,22 @@
return;
}
- // register all display if global magnification is enabled.
- final Display[] displays = mDisplayManager.getDisplays();
+ // 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.length; i++) {
- final Display display = displays[i];
- // Overlay display uses overlay window to simulate secondary displays in
- // one display. It's not a real display and there's no input events for it.
- // We should ignore it.
- if (display.getType() == Display.TYPE_OVERLAY) {
- continue;
- }
+ 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.length; i++) {
- final Display display = displays[i];
- // Overlay display uses overlay window to simulate secondary displays in
- // one display. It's not a real display and there's no input events for it.
- // We should ignore it.
- if (display.getType() == Display.TYPE_OVERLAY) {
- continue;
- }
+ // 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);
@@ -3812,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/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 9f7d33f..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,
@@ -2559,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) {
@@ -2574,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() {
@@ -2594,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;
}
@@ -2614,7 +2611,7 @@
Slog.v(TAG, "calling Augmented Autofill Service ("
+ remoteService.getComponentName().toShortString() + ") on view "
+ mCurrentViewId + " using suggestion mode "
- + smartSuggestionFlagsToString(mode)
+ + getSmartSuggestionModeToString(mode)
+ " when server returned null for session " + this.id);
}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 4f58d79..303734a 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -18,6 +18,7 @@
import static com.android.server.backup.BackupManagerService.TAG;
+import android.Manifest;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyManager;
@@ -41,9 +42,10 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
-import android.provider.Settings;
+import android.os.UserManager;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import java.io.File;
@@ -75,19 +77,25 @@
* system user is unlocked before any other users.
*/
public class Trampoline extends IBackupManager.Stub {
- // When this file is present, the backup service is inactive.
+ /**
+ * Name of file that disables the backup service. If this file exists, then backup is disabled
+ * for all users.
+ */
private static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress";
+ /**
+ * Name of file for non-system users that enables the backup service for the user. Backup is
+ * disabled by default in non-system users.
+ */
+ private static final String BACKUP_ACTIVATED_FILENAME = "backup-activated";
+
// Product-level suppression of backup/restore.
private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable";
private static final String BACKUP_THREAD = "backup";
- /** Values for setting {@link Settings.Global#BACKUP_MULTI_USER_ENABLED} */
- private static final int MULTI_USER_DISABLED = 0;
- private static final int MULTI_USER_ENABLED = 1;
-
private final Context mContext;
+ private final UserManager mUserManager;
private final boolean mGlobalDisable;
// Lock to write backup suppress files.
@@ -104,20 +112,13 @@
mHandlerThread = new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mUserManager = UserManager.get(context);
}
protected boolean isBackupDisabled() {
return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false);
}
- private boolean isMultiUserEnabled() {
- return Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.BACKUP_MULTI_USER_ENABLED,
- MULTI_USER_DISABLED)
- == MULTI_USER_ENABLED;
- }
-
protected int binderGetCallingUserId() {
return Binder.getCallingUserHandle().getIdentifier();
}
@@ -126,21 +127,65 @@
return Binder.getCallingUid();
}
- protected File getSuppressFileForUser(int userId) {
- return new File(UserBackupManagerFiles.getBaseStateDir(userId),
+ /** Stored in the system user's directory. */
+ protected File getSuppressFileForSystemUser() {
+ return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM),
BACKUP_SUPPRESS_FILENAME);
}
- protected void createBackupSuppressFileForUser(int userId) throws IOException {
- synchronized (mStateLock) {
- getSuppressFileForUser(userId).getParentFile().mkdirs();
- getSuppressFileForUser(userId).createNewFile();
+ /** Stored in the system user's directory and the file is indexed by the user it refers to. */
+ protected File getActivatedFileForNonSystemUser(int userId) {
+ return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM),
+ BACKUP_ACTIVATED_FILENAME + "-" + userId);
+ }
+
+ private void createFile(File file) throws IOException {
+ if (file.exists()) {
+ return;
+ }
+
+ file.getParentFile().mkdirs();
+ if (!file.createNewFile()) {
+ Slog.w(TAG, "Failed to create file " + file.getPath());
}
}
- private void deleteBackupSuppressFileForUser(int userId) {
- if (!getSuppressFileForUser(userId).delete()) {
- Slog.w(TAG, "Failed deleting backup suppressed file for user: " + userId);
+ private void deleteFile(File file) {
+ if (!file.exists()) {
+ return;
+ }
+
+ if (!file.delete()) {
+ Slog.w(TAG, "Failed to delete file " + file.getPath());
+ }
+ }
+
+ /**
+ * Deactivates the backup service for user {@code userId}. If this is the system user, it
+ * creates a suppress file which disables backup for all users. If this is a non-system user, it
+ * only deactivates backup for that user by deleting its activate file.
+ */
+ @GuardedBy("mStateLock")
+ private void deactivateBackupForUserLocked(int userId) throws IOException {
+ if (userId == UserHandle.USER_SYSTEM) {
+ createFile(getSuppressFileForSystemUser());
+ } else {
+ deleteFile(getActivatedFileForNonSystemUser(userId));
+ }
+ }
+
+ /**
+ * Enables the backup service for user {@code userId}. If this is the system user, it deletes
+ * the suppress file. If this is a non-system user, it creates the user's activate file. Note,
+ * deleting the suppress file does not automatically enable backup for non-system users, they
+ * need their own activate file in order to participate in the service.
+ */
+ @GuardedBy("mStateLock")
+ private void activateBackupForUserLocked(int userId) throws IOException {
+ if (userId == UserHandle.USER_SYSTEM) {
+ deleteFile(getSuppressFileForSystemUser());
+ } else {
+ createFile(getActivatedFileForNonSystemUser(userId));
}
}
@@ -148,24 +193,31 @@
// admin (device owner or profile owner).
private boolean isUserReadyForBackup(int userId) {
return mService != null && mService.getServiceUsers().get(userId) != null
- && !isBackupSuppressedForUser(userId);
+ && isBackupActivatedForUser(userId);
}
- private boolean isBackupSuppressedForUser(int userId) {
- // If backup is disabled for system user, it's disabled for all other users on device.
- if (getSuppressFileForUser(UserHandle.USER_SYSTEM).exists()) {
- return true;
+ /**
+ * Backup is activated for the system user if the suppress file does not exist. Backup is
+ * activated for non-system users if the suppress file does not exist AND the user's activated
+ * file exists.
+ */
+ private boolean isBackupActivatedForUser(int userId) {
+ if (getSuppressFileForSystemUser().exists()) {
+ return false;
}
- if (userId != UserHandle.USER_SYSTEM) {
- return getSuppressFileForUser(userId).exists();
- }
- return false;
+
+ return userId == UserHandle.USER_SYSTEM
+ || getActivatedFileForNonSystemUser(userId).exists();
}
protected Context getContext() {
return mContext;
}
+ protected UserManager getUserManager() {
+ return mUserManager;
+ }
+
protected BackupManagerService createBackupManagerService() {
return new BackupManagerService(mContext, this, mHandlerThread);
}
@@ -198,23 +250,17 @@
/**
* Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is unlocked.
- * Starts the backup service for this user if it's the system user or if the service supports
- * multi-user. Offloads work onto the handler thread {@link #mHandlerThread} to keep unlock time
- * low.
+ * Starts the backup service for this user if backup is active for this user. Offloads work onto
+ * the handler thread {@link #mHandlerThread} to keep unlock time low.
*/
void unlockUser(int userId) {
- if (userId != UserHandle.USER_SYSTEM && !isMultiUserEnabled()) {
- Slog.i(TAG, "Multi-user disabled, cannot start service for user: " + userId);
- return;
- }
-
postToHandler(() -> startServiceForUser(userId));
}
private void startServiceForUser(int userId) {
// We know that the user is unlocked here because it is called from setBackupServiceActive
// and unlockUser which have these guarantees. So we can check if the file exists.
- if (mService != null && !isBackupSuppressedForUser(userId)) {
+ if (mService != null && isBackupActivatedForUser(userId)) {
Slog.i(TAG, "Starting service for user: " + userId);
mService.startServiceForUser(userId);
}
@@ -225,11 +271,6 @@
* Offloads work onto the handler thread {@link #mHandlerThread} to keep stopping time low.
*/
void stopUser(int userId) {
- if (userId != UserHandle.USER_SYSTEM && !isMultiUserEnabled()) {
- Slog.i(TAG, "Multi-user disabled, cannot stop service for user: " + userId);
- return;
- }
-
postToHandler(
() -> {
if (mService != null) {
@@ -240,45 +281,63 @@
}
/**
- * Only privileged callers should be changing the backup state. This method only acts on {@link
- * UserHandle#USER_SYSTEM} and is a no-op if passed non-system users. Deactivating backup in the
- * system user also deactivates backup in all users.
- *
- * This call will only work if the calling {@code userID} is unlocked.
+ * The system user and managed profiles can only be acted on by callers in the system or root
+ * processes. Other users can be acted on by callers who have both android.permission.BACKUP and
+ * android.permission.INTERACT_ACROSS_USERS_FULL permissions.
+ */
+ private void enforcePermissionsOnUser(int userId) throws SecurityException {
+ boolean isRestrictedUser =
+ userId == UserHandle.USER_SYSTEM
+ || getUserManager().getUserInfo(userId).isManagedProfile();
+
+ if (isRestrictedUser) {
+ int caller = binderGetCallingUid();
+ if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) {
+ throw new SecurityException("No permission to configure backup activity");
+ }
+ } else {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.BACKUP, "No permission to configure backup activity");
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "No permission to configure backup activity");
+ }
+ }
+
+ /**
+ * Only privileged callers should be changing the backup state. Deactivating backup in the
+ * system user also deactivates backup in all users. We are not guaranteed that {@code userId}
+ * is unlocked at this point yet, so handle both cases.
*/
public void setBackupServiceActive(int userId, boolean makeActive) {
- int caller = binderGetCallingUid();
- if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) {
- throw new SecurityException("No permission to configure backup activity");
- }
+ enforcePermissionsOnUser(userId);
if (mGlobalDisable) {
Slog.i(TAG, "Backup service not supported");
return;
}
- if (userId != UserHandle.USER_SYSTEM) {
- Slog.i(TAG, "Cannot set backup service activity for non-system user: " + userId);
- return;
- }
-
- if (makeActive == isBackupServiceActive(userId)) {
- Slog.i(TAG, "No change in backup service activity");
- return;
- }
-
synchronized (mStateLock) {
Slog.i(TAG, "Making backup " + (makeActive ? "" : "in") + "active");
if (makeActive) {
if (mService == null) {
mService = createBackupManagerService();
}
- deleteBackupSuppressFileForUser(userId);
- startServiceForUser(userId);
+ try {
+ activateBackupForUserLocked(userId);
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to persist backup service activity");
+ }
+
+ // If the user is unlocked, we can start the backup service for it. Otherwise we
+ // will start the service when the user is unlocked as part of its unlock callback.
+ if (getUserManager().isUserUnlocked(userId)) {
+ startServiceForUser(userId);
+ }
} else {
try {
//TODO(b/121198006): what if this throws an exception?
- createBackupSuppressFileForUser(userId);
+ deactivateBackupForUserLocked(userId);
} catch (IOException e) {
Slog.e(TAG, "Unable to persist backup service inactivity");
}
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 fcd136c..e3dcb7d 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -77,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;
@@ -91,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;
@@ -145,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;
@@ -195,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;
@@ -277,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
@@ -302,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 = {
@@ -319,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
@@ -348,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(',');
@@ -409,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) {
@@ -925,7 +1081,7 @@
if (targetPackages != null && !targetPackages.contains(packageUser)) {
continue;
}
- if (adjustDeliveryTimeBasedOnStandbyBucketLocked(alarm)) {
+ if (adjustDeliveryTimeBasedOnBucketLocked(alarm)) {
batch.remove(alarm);
rescheduledAlarms.add(alarm);
}
@@ -1300,6 +1456,7 @@
synchronized (mLock) {
mHandler = new AlarmHandler();
mConstants = new Constants(mHandler);
+ mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
mNextWakeup = mNextNonWakeup = 0;
@@ -1583,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
*/
@@ -1608,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;
}
@@ -1629,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);
}
@@ -1696,7 +1905,7 @@
mAllowWhileIdleDispatches.add(ent);
}
}
- adjustDeliveryTimeBasedOnStandbyBucketLocked(a);
+ adjustDeliveryTimeBasedOnBucketLocked(a);
insertAndBatchAlarmLocked(a);
if (a.alarmClock != null) {
@@ -1915,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) {
@@ -2065,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();
@@ -3862,6 +4064,7 @@
obtainMessage(REMOVE_FOR_STOPPED, uid, 0).sendToTarget();
}
+ @Override
public void handleMessage(Message msg) {
switch (msg.what) {
case ALARM_EVENT: {
@@ -4030,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);
@@ -4131,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) {
@@ -4474,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 aa74600..1519c17 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.shared.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);
@@ -1869,6 +1884,12 @@
"ConnectivityService");
}
+ private void enforceControlAlwaysOnVpnPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONTROL_ALWAYS_ON_VPN,
+ "ConnectivityService");
+ }
+
private void enforceNetworkStackSettingsOrSetup() {
enforceAnyPermissionOf(
android.Manifest.permission.NETWORK_SETTINGS,
@@ -1876,6 +1897,12 @@
android.Manifest.permission.NETWORK_STACK);
}
+ private void enforceNetworkStackPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.NETWORK_STACK,
+ "ConnectivityService");
+ }
+
private boolean checkNetworkStackPermission() {
return PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
android.Manifest.permission.NETWORK_STACK);
@@ -2476,6 +2503,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 +2528,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 +2560,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 +2574,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");
@@ -3237,9 +3283,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;
@@ -3252,10 +3304,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);
@@ -3273,7 +3327,7 @@
!nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
return;
}
- showValidationNotification(nai, NotificationType.NO_INTERNET);
+ showNetworkNotification(nai, NotificationType.NO_INTERNET);
}
private void handleNetworkUnvalidated(NetworkAgentInfo nai) {
@@ -3282,7 +3336,7 @@
if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) {
- showValidationNotification(nai, NotificationType.LOST_INTERNET);
+ showNetworkNotification(nai, NotificationType.LOST_INTERNET);
}
}
@@ -3428,6 +3482,9 @@
case EVENT_DATA_SAVER_CHANGED:
handleRestrictBackgroundChanged(toBool(msg.arg1));
break;
+ case EVENT_TIMEOUT_NOTIFICATION:
+ mNotifier.clearNotification(msg.arg1, NotificationType.LOGGED_IN);
+ break;
}
}
}
@@ -3685,20 +3742,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);
}
}
@@ -3722,11 +3805,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();
@@ -4077,8 +4159,9 @@
}
@Override
- public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown) {
- enforceConnectivityInternalPermission();
+ public boolean setAlwaysOnVpnPackage(
+ int userId, String packageName, boolean lockdown, List<String> lockdownWhitelist) {
+ enforceControlAlwaysOnVpnPermission();
enforceCrossUserPermission(userId);
synchronized (mVpns) {
@@ -4092,11 +4175,11 @@
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
return false;
}
- if (!vpn.setAlwaysOnPackage(packageName, lockdown)) {
+ if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist)) {
return false;
}
if (!startAlwaysOnVpn(userId)) {
- vpn.setAlwaysOnPackage(null, false);
+ vpn.setAlwaysOnPackage(null, false, null);
return false;
}
}
@@ -4105,7 +4188,7 @@
@Override
public String getAlwaysOnVpnPackage(int userId) {
- enforceConnectivityInternalPermission();
+ enforceControlAlwaysOnVpnPermission();
enforceCrossUserPermission(userId);
synchronized (mVpns) {
@@ -4119,6 +4202,36 @@
}
@Override
+ public boolean isVpnLockdownEnabled(int userId) {
+ enforceControlAlwaysOnVpnPermission();
+ enforceCrossUserPermission(userId);
+
+ synchronized (mVpns) {
+ Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ Slog.w(TAG, "User " + userId + " has no Vpn configuration");
+ return false;
+ }
+ return vpn.getLockdown();
+ }
+ }
+
+ @Override
+ public List<String> getVpnLockdownWhitelist(int userId) {
+ enforceControlAlwaysOnVpnPermission();
+ enforceCrossUserPermission(userId);
+
+ synchronized (mVpns) {
+ Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ Slog.w(TAG, "User " + userId + " has no Vpn configuration");
+ return null;
+ }
+ return vpn.getLockdownWhitelist();
+ }
+ }
+
+ @Override
public int checkMobileProvisioning(int suggestedTimeOutMs) {
// TODO: Remove? Any reason to trigger a provisioning check?
return -1;
@@ -4347,7 +4460,7 @@
if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user "
+ userId);
- vpn.setAlwaysOnPackage(null, false);
+ vpn.setAlwaysOnPackage(null, false, null);
}
}
}
@@ -5893,12 +6006,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
@@ -5917,10 +6024,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
@@ -6185,6 +6298,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));
@@ -6216,7 +6340,7 @@
synchronized (mVpns) {
final String alwaysOnPackage = getAlwaysOnVpnPackage(userId);
if (alwaysOnPackage != null) {
- setAlwaysOnVpnPackage(userId, null, false);
+ setAlwaysOnVpnPackage(userId, null, false, null);
setVpnPackageAuthorization(alwaysOnPackage, userId, false);
}
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 8dcc1d5..d2c6354 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -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<>();
@@ -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);
@@ -1299,8 +1332,7 @@
if (provider == null) {
continue;
}
- if (!provider.isUseableLocked()
- && !updateRecord.mRealRequest.isLocationSettingsIgnored()) {
+ if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) {
continue;
}
@@ -1988,7 +2020,7 @@
}
// requests that ignore location settings will never provider notifications
- if (record.mRealRequest.isLocationSettingsIgnored()) {
+ if (isSettingsExemptLocked(record)) {
continue;
}
@@ -2052,8 +2084,7 @@
record.mReceiver.mAllowedResolutionLevel)) {
continue;
}
- if (!provider.isUseableLocked()
- && !record.mRealRequest.isLocationSettingsIgnored()) {
+ if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) {
continue;
}
@@ -2163,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
@@ -2306,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;
}
@@ -2418,7 +2468,7 @@
oldRecord.disposeLocked(false);
}
- if (!provider.isUseableLocked() && !request.isLocationSettingsIgnored()) {
+ 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);
@@ -3056,7 +3106,7 @@
Receiver receiver = r.mReceiver;
boolean receiverDead = false;
- if (!provider.isUseableLocked() && !r.mRealRequest.isLocationSettingsIgnored()) {
+ if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) {
continue;
}
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/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index dd2b33a..d025d73 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -16,13 +16,19 @@
package com.android.server.am;
+import static android.provider.DeviceConfig.ActivityManager.KEY_MAX_CACHED_PROCESSES;
+
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK;
+import android.app.ActivityThread;
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.OnPropertyChangedListener;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.KeyValueListParser;
import android.util.Slog;
@@ -34,7 +40,6 @@
final class ActivityManagerConstants extends ContentObserver {
// Key names stored in the settings value.
- private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
private static final String KEY_BACKGROUND_SETTLE_TIME = "background_settle_time";
private static final String KEY_FGSERVICE_MIN_SHOWN_TIME
= "fgservice_min_shown_time";
@@ -69,13 +74,6 @@
static final String KEY_PROCESS_START_ASYNC = "process_start_async";
static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time";
static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration";
- static final String KEY_USE_COMPACTION = "use_compaction";
- static final String KEY_COMPACT_ACTION_1 = "compact_action_1";
- static final String KEY_COMPACT_ACTION_2 = "compact_action_2";
- static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
- static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
- static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
- static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -106,13 +104,6 @@
private static final boolean DEFAULT_PROCESS_START_ASYNC = true;
private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000;
private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000;
- private static final boolean DEFAULT_USE_COMPACTION = false;
- public static final int DEFAULT_COMPACT_ACTION_1 = 1;
- public static final int DEFAULT_COMPACT_ACTION_2 = 3;
- public static final long DEFAULT_COMPACT_THROTTLE_1 = 5000;
- public static final long DEFAULT_COMPACT_THROTTLE_2 = 10000;
- public static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
- public static final long DEFAULT_COMPACT_THROTTLE_4 = 10000;
// Maximum number of cached processes we will allow.
public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
@@ -232,23 +223,6 @@
// this long.
public long TOP_TO_FGS_GRACE_DURATION = DEFAULT_TOP_TO_FGS_GRACE_DURATION;
- // Use compaction for background apps.
- public boolean USE_COMPACTION = DEFAULT_USE_COMPACTION;
-
- // Action for compactAppSome.
- public int COMPACT_ACTION_1 = DEFAULT_COMPACT_ACTION_1;
- // Action for compactAppFull;
- public int COMPACT_ACTION_2 = DEFAULT_COMPACT_ACTION_2;
-
- // How long we'll skip second compactAppSome after first compactAppSome
- public long COMPACT_THROTTLE_1 = DEFAULT_COMPACT_THROTTLE_1;
- // How long we'll skip compactAppSome after compactAppFull
- public long COMPACT_THROTTLE_2 = DEFAULT_COMPACT_THROTTLE_2;
- // How long we'll skip compactAppFull after compactAppSome
- public long COMPACT_THROTTLE_3 = DEFAULT_COMPACT_THROTTLE_3;
- // How long we'll skip second compactAppFull after first compactAppFull
- public long COMPACT_THROTTLE_4 = DEFAULT_COMPACT_THROTTLE_4;
-
// Indicates whether the activity starts logging is enabled.
// Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED
volatile boolean mFlagActivityStartsLoggingEnabled;
@@ -295,10 +269,19 @@
Settings.Global.getUriFor(
Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED);
+ private final OnPropertyChangedListener mOnDeviceConfigChangedListener =
+ new OnPropertyChangedListener() {
+ @Override
+ public void onPropertyChanged(String namespace, String name, String value) {
+ if (KEY_MAX_CACHED_PROCESSES.equals(name)) {
+ updateMaxCachedProcesses();
+ }
+ }
+ };
+
public ActivityManagerConstants(ActivityManagerService service, Handler handler) {
super(handler);
mService = service;
- updateMaxCachedProcesses();
}
public void start(ContentResolver resolver) {
@@ -309,6 +292,11 @@
updateConstants();
updateActivityStartsLoggingEnabled();
updateBackgroundActivityStartsEnabled();
+ DeviceConfig.addOnPropertyChangedListener(DeviceConfig.ActivityManager.NAMESPACE,
+ ActivityThread.currentApplication().getMainExecutor(),
+ mOnDeviceConfigChangedListener);
+ updateMaxCachedProcesses();
+
}
public void setOverrideMaxCachedProcesses(int value) {
@@ -347,8 +335,6 @@
// with defaults.
Slog.e("ActivityManagerConstants", "Bad activity manager config settings", e);
}
- MAX_CACHED_PROCESSES = mParser.getInt(KEY_MAX_CACHED_PROCESSES,
- DEFAULT_MAX_CACHED_PROCESSES);
BACKGROUND_SETTLE_TIME = mParser.getLong(KEY_BACKGROUND_SETTLE_TIME,
DEFAULT_BACKGROUND_SETTLE_TIME);
FGSERVICE_MIN_SHOWN_TIME = mParser.getLong(KEY_FGSERVICE_MIN_SHOWN_TIME,
@@ -406,13 +392,9 @@
DEFAULT_MEMORY_INFO_THROTTLE_TIME);
TOP_TO_FGS_GRACE_DURATION = mParser.getDurationMillis(KEY_TOP_TO_FGS_GRACE_DURATION,
DEFAULT_TOP_TO_FGS_GRACE_DURATION);
- USE_COMPACTION = mParser.getBoolean(KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
- COMPACT_ACTION_1 = mParser.getInt(KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1);
- COMPACT_ACTION_2 = mParser.getInt(KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2);
- COMPACT_THROTTLE_1 = mParser.getLong(KEY_COMPACT_THROTTLE_1, DEFAULT_COMPACT_THROTTLE_1);
- COMPACT_THROTTLE_2 = mParser.getLong(KEY_COMPACT_THROTTLE_2, DEFAULT_COMPACT_THROTTLE_2);
- COMPACT_THROTTLE_3 = mParser.getLong(KEY_COMPACT_THROTTLE_3, DEFAULT_COMPACT_THROTTLE_3);
- COMPACT_THROTTLE_4 = mParser.getLong(KEY_COMPACT_THROTTLE_4, DEFAULT_COMPACT_THROTTLE_4);
+
+ // For new flags that are intended for server-side experiments, please use the new
+ // DeviceConfig package.
updateMaxCachedProcesses();
}
@@ -429,8 +411,19 @@
}
private void updateMaxCachedProcesses() {
- CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0
- ? MAX_CACHED_PROCESSES : mOverrideMaxCachedProcesses;
+ String maxCachedProcessesFlag = DeviceConfig.getProperty(
+ DeviceConfig.ActivityManager.NAMESPACE, KEY_MAX_CACHED_PROCESSES);
+ try {
+ CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0
+ ? (TextUtils.isEmpty(maxCachedProcessesFlag)
+ ? DEFAULT_MAX_CACHED_PROCESSES : Integer.parseInt(maxCachedProcessesFlag))
+ : mOverrideMaxCachedProcesses;
+ } catch (NumberFormatException e) {
+ // Bad flag value from Phenotype, revert to default.
+ Slog.e("ActivityManagerConstants",
+ "Unable to parse flag for max_cached_processes: " + maxCachedProcessesFlag, e);
+ CUR_MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
+ }
CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES);
// Note the trim levels do NOT depend on the override process limit, we want
@@ -503,8 +496,6 @@
pw.println(MEMORY_INFO_THROTTLE_TIME);
pw.print(" "); pw.print(KEY_TOP_TO_FGS_GRACE_DURATION); pw.print("=");
pw.println(TOP_TO_FGS_GRACE_DURATION);
- pw.print(" "); pw.print(KEY_USE_COMPACTION); pw.print("=");
- pw.println(USE_COMPACTION);
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b24290f..eb643b6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -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: {
@@ -7228,6 +7217,7 @@
mActivityTaskManager.installSystemProviders();
mDevelopmentSettingsObserver = new DevelopmentSettingsObserver();
SettingsToPropertiesMapper.start(mContext.getContentResolver());
+ mOomAdjuster.initSettings();
// Now that the settings provider is published we can consider sending
// in a rescue party.
@@ -9340,6 +9330,7 @@
synchronized(this) {
mConstants.dump(pw);
+ mOomAdjuster.dumpAppCompactorSettings(pw);
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
@@ -9739,6 +9730,7 @@
} else if ("settings".equals(cmd)) {
synchronized (this) {
mConstants.dump(pw);
+ mOomAdjuster.dumpAppCompactorSettings(pw);
}
} else if ("services".equals(cmd) || "s".equals(cmd)) {
if (dumpClient) {
@@ -14656,8 +14648,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:
diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java
index fd402cc..bb55ec31 100644
--- a/services/core/java/com/android/server/am/AppCompactor.java
+++ b/services/core/java/com/android/server/am/AppCompactor.java
@@ -16,34 +16,63 @@
package com.android.server.am;
-import com.android.internal.annotations.GuardedBy;
+import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_1;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_2;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_1;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_2;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_3;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_4;
+import static android.provider.DeviceConfig.ActivityManager.KEY_USE_COMPACTION;
import android.app.ActivityManager;
-
+import android.app.ActivityThread;
import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
import android.os.Trace;
-
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.OnPropertyChangedListener;
+import android.text.TextUtils;
import android.util.EventLog;
import android.util.StatsLog;
-import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
-
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.ServiceThread;
import java.io.FileOutputStream;
-import java.io.IOException;
+import java.io.PrintWriter;
import java.util.ArrayList;
public final class AppCompactor {
- /**
- * Processes to compact.
- */
- final ArrayList<ProcessRecord> mPendingCompactionProcesses = new ArrayList<ProcessRecord>();
+ // Phenotype sends int configurations and we map them to the strings we'll use on device,
+ // preventing a weird string value entering the kernel.
+ private static final int COMPACT_ACTION_FILE_FLAG = 1;
+ private static final int COMPACT_ACTION_ANON_FLAG = 2;
+ private static final int COMPACT_ACTION_FULL_FLAG = 3;
+ private static final String COMPACT_ACTION_FILE = "file";
+ private static final String COMPACT_ACTION_ANON = "anon";
+ private static final String COMPACT_ACTION_FULL = "all";
+
+ // Defaults for phenotype flags.
+ @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
+ @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG;
+ @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG;
+ @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
+ @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
+ @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
+ @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_4 = 10_000;
+
+ @VisibleForTesting
+ interface PropertyChangedCallbackForTest {
+ void onPropertyChanged();
+ }
+ private PropertyChangedCallbackForTest mTestCallback;
+
+ // Handler constants.
static final int COMPACT_PROCESS_SOME = 1;
static final int COMPACT_PROCESS_FULL = 2;
static final int COMPACT_PROCESS_MSG = 1;
@@ -57,70 +86,106 @@
*/
final ServiceThread mCompactionThread;
- final private Handler mCompactionHandler;
+ private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
+ new ArrayList<ProcessRecord>();
+ private final ActivityManagerService mAm;
+ private final OnPropertyChangedListener mOnFlagsChangedListener =
+ new OnPropertyChangedListener() {
+ @Override
+ public void onPropertyChanged(String namespace, String name, String value) {
+ synchronized (mPhenotypeFlagLock) {
+ if (KEY_USE_COMPACTION.equals(name)) {
+ updateUseCompaction();
+ } else if (KEY_COMPACT_ACTION_1.equals(name)
+ || KEY_COMPACT_ACTION_2.equals(name)) {
+ updateCompactionActions();
+ } else if (KEY_COMPACT_THROTTLE_1.equals(name)
+ || KEY_COMPACT_THROTTLE_2.equals(name)
+ || KEY_COMPACT_THROTTLE_3.equals(name)
+ || KEY_COMPACT_THROTTLE_4.equals(name)) {
+ updateCompactionThrottles();
+ }
+ }
+ if (mTestCallback != null) {
+ mTestCallback.onPropertyChanged();
+ }
+ }
+ };
- final private ActivityManagerService mAm;
- final private ActivityManagerConstants mConstants;
+ private final Object mPhenotypeFlagLock = new Object();
- final private String COMPACT_ACTION_FILE = "file";
- final private String COMPACT_ACTION_ANON = "anon";
- final private String COMPACT_ACTION_FULL = "all";
+ // Configured by phenotype. Updates from the server take effect immediately.
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting String mCompactActionSome =
+ compactActionIntToString(DEFAULT_COMPACT_ACTION_1);
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting String mCompactActionFull =
+ compactActionIntToString(DEFAULT_COMPACT_ACTION_2);
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting long mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting long mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting long mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting long mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
+ @GuardedBy("mPhenotypeFlagLock")
+ private boolean mUseCompaction = DEFAULT_USE_COMPACTION;
- final private String compactActionSome;
- final private String compactActionFull;
-
- final private long throttleSomeSome;
- final private long throttleSomeFull;
- final private long throttleFullSome;
- final private long throttleFullFull;
+ // Handler on which compaction runs.
+ private Handler mCompactionHandler;
public AppCompactor(ActivityManagerService am) {
mAm = am;
- mConstants = am.mConstants;
-
mCompactionThread = new ServiceThread("CompactionThread",
THREAD_PRIORITY_FOREGROUND, true);
- mCompactionThread.start();
- mCompactionHandler = new MemCompactionHandler(this);
-
- switch(mConstants.COMPACT_ACTION_1) {
- case 1:
- compactActionSome = COMPACT_ACTION_FILE;
- break;
- case 2:
- compactActionSome = COMPACT_ACTION_ANON;
- break;
- case 3:
- compactActionSome = COMPACT_ACTION_FULL;
- break;
- default:
- compactActionSome = COMPACT_ACTION_FILE;
- break;
- }
-
- switch(mConstants.COMPACT_ACTION_2) {
- case 1:
- compactActionFull = COMPACT_ACTION_FILE;
- break;
- case 2:
- compactActionFull = COMPACT_ACTION_ANON;
- break;
- case 3:
- compactActionFull = COMPACT_ACTION_FULL;
- break;
- default:
- compactActionFull = COMPACT_ACTION_FULL;
- break;
- }
-
- throttleSomeSome = mConstants.COMPACT_THROTTLE_1;
- throttleSomeFull = mConstants.COMPACT_THROTTLE_2;
- throttleFullSome = mConstants.COMPACT_THROTTLE_3;
- throttleFullFull = mConstants.COMPACT_THROTTLE_4;
}
- // Must be called while holding AMS lock.
- final void compactAppSome(ProcessRecord app) {
+ @VisibleForTesting
+ AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
+ this(am);
+ mTestCallback = callback;
+ }
+
+ /**
+ * Reads phenotype config to determine whether app compaction is enabled or not and
+ * starts the background thread if necessary.
+ */
+ public void init() {
+ DeviceConfig.addOnPropertyChangedListener(DeviceConfig.ActivityManager.NAMESPACE,
+ ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener);
+ synchronized (mPhenotypeFlagLock) {
+ updateUseCompaction();
+ updateCompactionActions();
+ updateCompactionThrottles();
+ }
+ }
+
+ /**
+ * Returns whether compaction is enabled.
+ */
+ public boolean useCompaction() {
+ synchronized (mPhenotypeFlagLock) {
+ return mUseCompaction;
+ }
+ }
+
+ @GuardedBy("mAm")
+ void dump(PrintWriter pw) {
+ pw.println("AppCompactor settings");
+ synchronized (mPhenotypeFlagLock) {
+ pw.println(" " + KEY_USE_COMPACTION + "=" + mUseCompaction);
+ pw.println(" " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome);
+ pw.println(" " + KEY_COMPACT_ACTION_2 + "=" + mCompactActionFull);
+ pw.println(" " + KEY_COMPACT_THROTTLE_1 + "=" + mCompactThrottleSomeSome);
+ pw.println(" " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull);
+ pw.println(" " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome);
+ pw.println(" " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull);
+ }
+ }
+
+ @GuardedBy("mAm")
+ void compactAppSome(ProcessRecord app) {
app.reqCompactAction = COMPACT_PROCESS_SOME;
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
@@ -128,8 +193,8 @@
COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
}
- // Must be called while holding AMS lock.
- final void compactAppFull(ProcessRecord app) {
+ @GuardedBy("mAm")
+ void compactAppFull(ProcessRecord app) {
app.reqCompactAction = COMPACT_PROCESS_FULL;
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
@@ -137,97 +202,205 @@
COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
}
- final class MemCompactionHandler extends Handler {
- AppCompactor mAc;
- private MemCompactionHandler(AppCompactor ac) {
- super(ac.mCompactionThread.getLooper());
- mAc = ac;
+ /**
+ * Reads the flag value from DeviceConfig to determine whether app compaction
+ * should be enabled, and starts/stops the compaction thread as needed.
+ */
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateUseCompaction() {
+ String useCompactionFlag =
+ DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_USE_COMPACTION);
+ mUseCompaction = TextUtils.isEmpty(useCompactionFlag)
+ ? DEFAULT_USE_COMPACTION : Boolean.parseBoolean(useCompactionFlag);
+ if (mUseCompaction && !mCompactionThread.isAlive()) {
+ mCompactionThread.start();
+ mCompactionHandler = new MemCompactionHandler();
+ }
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateCompactionActions() {
+ String compactAction1Flag =
+ DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_1);
+ String compactAction2Flag =
+ DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_2);
+
+ int compactAction1 = DEFAULT_COMPACT_ACTION_1;
+ try {
+ compactAction1 = TextUtils.isEmpty(compactAction1Flag)
+ ? DEFAULT_COMPACT_ACTION_1 : Integer.parseInt(compactAction1Flag);
+ } catch (NumberFormatException e) {
+ // Do nothing, leave default.
+ }
+
+ int compactAction2 = DEFAULT_COMPACT_ACTION_2;
+ try {
+ compactAction2 = TextUtils.isEmpty(compactAction2Flag)
+ ? DEFAULT_COMPACT_ACTION_2 : Integer.parseInt(compactAction2Flag);
+ } catch (NumberFormatException e) {
+ // Do nothing, leave default.
+ }
+
+ mCompactActionSome = compactActionIntToString(compactAction1);
+ mCompactActionFull = compactActionIntToString(compactAction2);
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateCompactionThrottles() {
+ boolean useThrottleDefaults = false;
+ String throttleSomeSomeFlag =
+ DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_1);
+ String throttleSomeFullFlag =
+ DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_2);
+ String throttleFullSomeFlag =
+ DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_3);
+ String throttleFullFullFlag =
+ DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_4);
+
+ if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag)
+ || TextUtils.isEmpty(throttleFullSomeFlag)
+ || TextUtils.isEmpty(throttleFullFullFlag)) {
+ // Set defaults for all if any are not set.
+ useThrottleDefaults = true;
+ } else {
+ try {
+ mCompactThrottleSomeSome = Integer.parseInt(throttleSomeSomeFlag);
+ mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag);
+ mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag);
+ mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag);
+ } catch (NumberFormatException e) {
+ useThrottleDefaults = true;
+ }
+ }
+
+ if (useThrottleDefaults) {
+ mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
+ mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
+ mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
+ mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
+ }
+ }
+
+ @VisibleForTesting
+ static String compactActionIntToString(int action) {
+ switch(action) {
+ case COMPACT_ACTION_FILE_FLAG:
+ return COMPACT_ACTION_FILE;
+ case COMPACT_ACTION_ANON_FLAG:
+ return COMPACT_ACTION_ANON;
+ case COMPACT_ACTION_FULL_FLAG:
+ return COMPACT_ACTION_FULL;
+ default:
+ return COMPACT_ACTION_FILE;
+ }
+ }
+
+ private final class MemCompactionHandler extends Handler {
+ private MemCompactionHandler() {
+ super(mCompactionThread.getLooper());
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case COMPACT_PROCESS_MSG: {
- long start = SystemClock.uptimeMillis();
- ProcessRecord proc;
- int pid;
- String action;
- final String name;
- int pendingAction, lastCompactAction;
- long lastCompactTime;
- synchronized(mAc.mAm) {
- proc = mAc.mPendingCompactionProcesses.remove(0);
+ case COMPACT_PROCESS_MSG: {
+ long start = SystemClock.uptimeMillis();
+ ProcessRecord proc;
+ int pid;
+ String action;
+ final String name;
+ int pendingAction, lastCompactAction;
+ long lastCompactTime;
+ synchronized (mAm) {
+ proc = mPendingCompactionProcesses.remove(0);
- // don't compact if the process has returned to perceptible
- if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
+ // don't compact if the process has returned to perceptible
+ if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
+ return;
+ }
+
+ pid = proc.pid;
+ name = proc.processName;
+ pendingAction = proc.reqCompactAction;
+ lastCompactAction = proc.lastCompactAction;
+ lastCompactTime = proc.lastCompactTime;
+ }
+
+ if (pid == 0) {
+ // not a real process, either one being launched or one being killed
return;
}
- pid = proc.pid;
- name = proc.processName;
- pendingAction = proc.reqCompactAction;
- lastCompactAction = proc.lastCompactAction;
- lastCompactTime = proc.lastCompactTime;
- }
- if (pid == 0) {
- // not a real process, either one being launched or one being killed
- return;
- }
+ // basic throttling
+ // use the Phenotype flag knobs to determine whether current/prevous
+ // compaction combo should be throtted or not
- // basic throttling
- // use the ActivityManagerConstants knobs to determine whether current/prevous
- // compaction combo should be throtted or not
- if (pendingAction == COMPACT_PROCESS_SOME) {
- if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleSomeSome)) ||
- (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleSomeFull))) {
- return;
- }
- } else {
- if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleFullSome)) ||
- (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleFullFull))) {
- return;
- }
- }
-
- try {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " +
- ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") +
- ": " + name);
- long[] rssBefore = Process.getRss(pid);
- FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
+ // Note that we explicitly don't take mPhenotypeFlagLock here as the flags
+ // should very seldom change, and taking the risk of using the wrong action is
+ // preferable to taking the lock for every single compaction action.
if (pendingAction == COMPACT_PROCESS_SOME) {
- action = compactActionSome;
+ if ((lastCompactAction == COMPACT_PROCESS_SOME
+ && (start - lastCompactTime < mCompactThrottleSomeSome))
+ || (lastCompactAction == COMPACT_PROCESS_FULL
+ && (start - lastCompactTime
+ < mCompactThrottleSomeFull))) {
+ return;
+ }
} else {
- action = compactActionFull;
+ if ((lastCompactAction == COMPACT_PROCESS_SOME
+ && (start - lastCompactTime < mCompactThrottleFullSome))
+ || (lastCompactAction == COMPACT_PROCESS_FULL
+ && (start - lastCompactTime
+ < mCompactThrottleFullFull))) {
+ return;
+ }
}
- fos.write(action.getBytes());
- fos.close();
- long[] rssAfter = Process.getRss(pid);
- long end = SystemClock.uptimeMillis();
- long time = end - start;
- EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
- rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
- rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
- lastCompactAction, lastCompactTime, msg.arg1, msg.arg2);
- StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction,
- rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
- rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
- lastCompactAction, lastCompactTime, msg.arg1,
- ActivityManager.processStateAmToProto(msg.arg2));
- synchronized(mAc.mAm) {
- proc.lastCompactTime = end;
- proc.lastCompactAction = pendingAction;
+
+ if (pendingAction == COMPACT_PROCESS_SOME) {
+ action = mCompactActionSome;
+ } else {
+ action = mCompactActionFull;
}
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } catch (Exception e) {
- // nothing to do, presumably the process died
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
+ + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
+ + ": " + name);
+ long[] rssBefore = Process.getRss(pid);
+ FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
+ fos.write(action.getBytes());
+ fos.close();
+ long[] rssAfter = Process.getRss(pid);
+ long end = SystemClock.uptimeMillis();
+ long time = end - start;
+ EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
+ rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
+ rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
+ lastCompactAction, lastCompactTime, msg.arg1, msg.arg2);
+ StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction,
+ rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
+ rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
+ lastCompactAction, lastCompactTime, msg.arg1,
+ ActivityManager.processStateAmToProto(msg.arg2));
+ synchronized (mAm) {
+ proc.lastCompactTime = end;
+ proc.lastCompactAction = pendingAction;
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ } catch (Exception e) {
+ // nothing to do, presumably the process died
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
}
}
- }
}
}
-
-
}
diff --git a/services/core/java/com/android/server/am/BaseErrorDialog.java b/services/core/java/com/android/server/am/BaseErrorDialog.java
index aabb587..dc9a4bf 100644
--- a/services/core/java/com/android/server/am/BaseErrorDialog.java
+++ b/services/core/java/com/android/server/am/BaseErrorDialog.java
@@ -16,8 +16,6 @@
package com.android.server.am;
-import com.android.internal.R;
-
import android.app.AlertDialog;
import android.content.Context;
import android.os.Handler;
@@ -26,6 +24,8 @@
import android.view.WindowManager;
import android.widget.Button;
+import com.android.internal.R;
+
public class BaseErrorDialog extends AlertDialog {
private static final int ENABLE_BUTTONS = 0;
private static final int DISABLE_BUTTONS = 1;
@@ -36,7 +36,7 @@
super(context, com.android.internal.R.style.Theme_DeviceDefault_Dialog_AppError);
context.assertRuntimeOverlayThemable();
- getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
WindowManager.LayoutParams attrs = getWindow().getAttributes();
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index d3953b5..3d69aa8 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -61,6 +61,8 @@
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS, String.class);
sGlobalSettingToTypeMap.put(
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES, String.class);
+ sGlobalSettingToTypeMap.put(
+ Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST, String.class);
sGlobalSettingToTypeMap.put(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, int.class);
sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_APP, String.class);
sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYERS, String.class);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index be910d4..1e03f6c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -134,10 +134,11 @@
mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
mConstants = mService.mConstants;
- // mConstants can be null under test, which causes AppCompactor to crash
- if (mConstants != null) {
- mAppCompact = new AppCompactor(mService);
- }
+ mAppCompact = new AppCompactor(mService);
+ }
+
+ void initSettings() {
+ mAppCompact.init();
}
/**
@@ -1679,7 +1680,7 @@
if (app.curAdj != app.setAdj) {
// don't compact during bootup
- if (mConstants.USE_COMPACTION && mService.mBooted) {
+ if (mAppCompact.useCompaction() && mService.mBooted) {
// Perform a minor compaction when a perceptible app becomes the prev/home app
// Perform a major compaction when any app enters cached
// reminder: here, setAdj is previous state, curAdj is upcoming state
@@ -2104,4 +2105,8 @@
+ " mNewNumServiceProcs=" + mNewNumServiceProcs);
}
+ @GuardedBy("mService")
+ void dumpAppCompactorSettings(PrintWriter pw) {
+ mAppCompact.dump(pw);
+ }
}
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 55cca95..0b27a8a 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -379,6 +379,10 @@
if (userId == UserHandle.USER_CURRENT) {
userId = controller.mUserController.getCurrentOrTargetUserId();
}
+ // temporarily allow receivers and services to open activities from background if the
+ // PendingIntent.send() caller was foreground at the time of sendInner() call
+ final boolean allowTrampoline = uid != callingUid
+ && controller.mAtmInternal.isUidForeground(callingUid);
switch (key.type) {
case ActivityManager.INTENT_SENDER_ACTIVITY:
@@ -419,7 +423,8 @@
uid, finalIntent, resolvedType, finishedReceiver, code, null, null,
requiredPermission, options, (finishedReceiver != null),
false, userId,
- mAllowBgActivityStartsForBroadcastSender.contains(whitelistToken));
+ mAllowBgActivityStartsForBroadcastSender.contains(whitelistToken)
+ || allowTrampoline);
if (sent == ActivityManager.BROADCAST_SUCCESS) {
sendFinish = false;
}
@@ -433,7 +438,8 @@
controller.mAmInternal.startServiceInPackage(uid, finalIntent, resolvedType,
key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE,
key.packageName, userId,
- mAllowBgActivityStartsForServiceSender.contains(whitelistToken));
+ mAllowBgActivityStartsForServiceSender.contains(whitelistToken)
+ || allowTrampoline);
} catch (RuntimeException e) {
Slog.w(TAG, "Unable to send startService intent", e);
} catch (TransactionTooLargeException e) {
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/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 c72c9dd..2508844 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -151,7 +151,7 @@
.divide(BigInteger.valueOf(100));
}
// How many routes to evaluate before bailing and declaring this Vpn should provide
- // the INTERNET capability. This is necessary because computing the adress space is
+ // the INTERNET capability. This is necessary because computing the address space is
// O(n²) and this is running in the system service, so a limit is needed to alleviate
// the risk of attack.
// This is taken as a total of IPv4 + IPV6 routes for simplicity, but the algorithm
@@ -194,6 +194,12 @@
private boolean mLockdown = false;
/**
+ * Set of packages in addition to the VPN app itself that can access the network directly when
+ * VPN is not connected even if {@code mLockdown} is set.
+ */
+ private @NonNull List<String> mLockdownWhitelist = Collections.emptyList();
+
+ /**
* List of UIDs for which networking should be blocked until VPN is ready, during brief periods
* when VPN is not running. For example, during system startup or after a crash.
* @see mLockdown
@@ -320,9 +326,9 @@
*
* Used to enable/disable legacy VPN lockdown.
*
- * This uses the same ip rule mechanism as {@link #setAlwaysOnPackage(String, boolean)};
- * previous settings from calling that function will be replaced and saved with the
- * always-on state.
+ * This uses the same ip rule mechanism as
+ * {@link #setAlwaysOnPackage(String, boolean, List<String>)}; previous settings from calling
+ * that function will be replaced and saved with the always-on state.
*
* @param lockdown whether to prevent all traffic outside of a VPN.
*/
@@ -419,12 +425,14 @@
*
* @param packageName the package to designate as always-on VPN supplier.
* @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
+ * @param lockdownWhitelist packages to be whitelisted from lockdown.
* @return {@code true} if the package has been set as always-on, {@code false} otherwise.
*/
- public synchronized boolean setAlwaysOnPackage(String packageName, boolean lockdown) {
+ public synchronized boolean setAlwaysOnPackage(
+ String packageName, boolean lockdown, List<String> lockdownWhitelist) {
enforceControlPermissionOrInternalCaller();
- if (setAlwaysOnPackageInternal(packageName, lockdown)) {
+ if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist)) {
saveAlwaysOnPackage();
return true;
}
@@ -439,15 +447,27 @@
*
* @param packageName the package to designate as always-on VPN supplier.
* @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
+ * @param lockdownWhitelist packages to be whitelisted from lockdown. This is only used if
+ * {@code lockdown} is {@code true}. Packages must not contain commas.
* @return {@code true} if the package has been set as always-on, {@code false} otherwise.
*/
@GuardedBy("this")
- private boolean setAlwaysOnPackageInternal(String packageName, boolean lockdown) {
+ private boolean setAlwaysOnPackageInternal(
+ String packageName, boolean lockdown, List<String> lockdownWhitelist) {
if (VpnConfig.LEGACY_VPN.equals(packageName)) {
Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
return false;
}
+ if (lockdownWhitelist != null) {
+ for (String pkg : lockdownWhitelist) {
+ if (pkg.contains(",")) {
+ Log.w(TAG, "Not setting always-on vpn, invalid whitelisted package: " + pkg);
+ return false;
+ }
+ }
+ }
+
if (packageName != null) {
// Pre-authorize new always-on VPN package.
if (!setPackageAuthorization(packageName, true)) {
@@ -460,13 +480,18 @@
}
mLockdown = (mAlwaysOn && lockdown);
+ mLockdownWhitelist = (mLockdown && lockdownWhitelist != null)
+ ? Collections.unmodifiableList(new ArrayList<>(lockdownWhitelist))
+ : Collections.emptyList();
+
if (isCurrentPreparedPackage(packageName)) {
updateAlwaysOnNotification(mNetworkInfo.getDetailedState());
+ setVpnForcedLocked(mLockdown);
} else {
// Prepare this app. The notification will update as a side-effect of updateState().
+ // It also calls setVpnForcedLocked().
prepareInternal(packageName);
}
- setVpnForcedLocked(mLockdown);
return true;
}
@@ -478,7 +503,6 @@
* @return the package name of the VPN controller responsible for always-on VPN,
* or {@code null} if none is set or always-on VPN is controlled through
* lockdown instead.
- * @hide
*/
public synchronized String getAlwaysOnPackage() {
enforceControlPermissionOrInternalCaller();
@@ -486,6 +510,13 @@
}
/**
+ * @return an immutable list of packages whitelisted from always-on VPN lockdown.
+ */
+ public synchronized List<String> getLockdownWhitelist() {
+ return mLockdown ? mLockdownWhitelist : null;
+ }
+
+ /**
* Save the always-on package and lockdown config into Settings.Secure
*/
@GuardedBy("this")
@@ -496,6 +527,9 @@
getAlwaysOnPackage(), mUserHandle);
mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
(mAlwaysOn && mLockdown ? 1 : 0), mUserHandle);
+ mSystemServices.settingsSecurePutStringForUser(
+ Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST,
+ String.join(",", mLockdownWhitelist), mUserHandle);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -512,7 +546,11 @@
Settings.Secure.ALWAYS_ON_VPN_APP, mUserHandle);
final boolean alwaysOnLockdown = mSystemServices.settingsSecureGetIntForUser(
Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, 0 /*default*/, mUserHandle) != 0;
- setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown);
+ final String whitelistString = mSystemServices.settingsSecureGetStringForUser(
+ Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, mUserHandle);
+ final List<String> whitelistedPackages = TextUtils.isEmpty(whitelistString)
+ ? Collections.emptyList() : Arrays.asList(whitelistString.split(","));
+ setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown, whitelistedPackages);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -532,7 +570,7 @@
}
// Remove always-on VPN if it's not supported.
if (!isAlwaysOnPackageSupported(alwaysOnPackage)) {
- setAlwaysOnPackage(null, false);
+ setAlwaysOnPackage(null, false, null);
return false;
}
// Skip if the service is already established. This isn't bulletproof: it's not bound
@@ -793,6 +831,8 @@
}
}
+ lp.setHttpProxy(mConfig.proxyInfo);
+
if (!allowIPv4) {
lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
}
@@ -1247,9 +1287,10 @@
}
/**
- * Restrict network access from all UIDs affected by this {@link Vpn}, apart from the VPN
- * service app itself, to only sockets that have had {@code protect()} called on them. All
- * non-VPN traffic is blocked via a {@code PROHIBIT} response from the kernel.
+ * Restricts network access from all UIDs affected by this {@link Vpn}, apart from the VPN
+ * service app itself and whitelisted packages, to only sockets that have had {@code protect()}
+ * called on them. All non-VPN traffic is blocked via a {@code PROHIBIT} response from the
+ * kernel.
*
* The exception for the VPN UID isn't technically necessary -- setup should use protected
* sockets -- but in practice it saves apps that don't protect their sockets from breaking.
@@ -1265,8 +1306,13 @@
*/
@GuardedBy("this")
private void setVpnForcedLocked(boolean enforce) {
- final List<String> exemptedPackages =
- isNullOrLegacyVpn(mPackage) ? null : Collections.singletonList(mPackage);
+ final List<String> exemptedPackages;
+ if (isNullOrLegacyVpn(mPackage)) {
+ exemptedPackages = null;
+ } else {
+ exemptedPackages = new ArrayList<>(mLockdownWhitelist);
+ exemptedPackages.add(mPackage);
+ }
final Set<UidRange> removedRanges = new ArraySet<>(mBlockedUsers);
Set<UidRange> addedRanges = Collections.emptySet();
diff --git a/services/core/java/com/android/server/job/JobConcurrencyManager.java b/services/core/java/com/android/server/job/JobConcurrencyManager.java
index 4d9b5f5..bec1947 100644
--- a/services/core/java/com/android/server/job/JobConcurrencyManager.java
+++ b/services/core/java/com/android/server/job/JobConcurrencyManager.java
@@ -36,6 +36,7 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.StatLogger;
import com.android.server.job.JobSchedulerService.Constants;
+import com.android.server.job.JobSchedulerService.MaxJobCountsPerMemoryTrimLevel;
import com.android.server.job.controllers.JobStatus;
import com.android.server.job.controllers.StateController;
@@ -148,14 +149,14 @@
Slog.d(TAG, "Interactive: " + interactive);
}
- final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis();
if (interactive) {
- mLastScreenOnRealtime = now;
+ mLastScreenOnRealtime = nowRealtime;
mEffectiveInteractiveState = true;
mHandler.removeCallbacks(mRampUpForScreenOff);
} else {
- mLastScreenOffRealtime = now;
+ mLastScreenOffRealtime = nowRealtime;
// Set mEffectiveInteractiveState to false after the delay, when we may increase
// the concurrency.
@@ -232,38 +233,24 @@
private void updateMaxCountsLocked() {
refreshSystemStateLocked();
- if (mEffectiveInteractiveState) {
- // Screen on
- switch (mLastMemoryTrimLevel) {
- case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_MODERATE;
- break;
- case ProcessStats.ADJ_MEM_FACTOR_LOW:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_LOW;
- break;
- case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_CRITICAL;
- break;
- default:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_NORMAL;
- break;
- }
- } else {
- // Screen off
- switch (mLastMemoryTrimLevel) {
- case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_MODERATE;
- break;
- case ProcessStats.ADJ_MEM_FACTOR_LOW:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_LOW;
- break;
- case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_CRITICAL;
- break;
- default:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_NORMAL;
- break;
- }
+ final MaxJobCountsPerMemoryTrimLevel jobCounts = mEffectiveInteractiveState
+ ? mConstants.MAX_JOB_COUNTS_SCREEN_ON
+ : mConstants.MAX_JOB_COUNTS_SCREEN_OFF;
+
+
+ switch (mLastMemoryTrimLevel) {
+ case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
+ mMaxJobCounts = jobCounts.moderate;
+ break;
+ case ProcessStats.ADJ_MEM_FACTOR_LOW:
+ mMaxJobCounts = jobCounts.low;
+ break;
+ case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
+ mMaxJobCounts = jobCounts.critical;
+ break;
+ default:
+ mMaxJobCounts = jobCounts.normal;
+ break;
}
}
@@ -303,7 +290,7 @@
// Initialize the work variables and also count running jobs.
mJobCountTracker.reset(
- mMaxJobCounts.getTotalMax(),
+ mMaxJobCounts.getMaxTotal(),
mMaxJobCounts.getMaxBg(),
mMaxJobCounts.getMinBg());
@@ -482,10 +469,7 @@
}
- public void dumpLocked(IndentingPrintWriter pw) {
- final long now = System.currentTimeMillis();
- final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis();
-
+ public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
pw.println("Concurrency:");
pw.increaseIndent();
@@ -522,19 +506,36 @@
}
}
- public void dumpProtoLocked(ProtoOutputStream proto) {
- // TODO Implement it.
+ public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) {
+ final long token = proto.start(tag);
+
+ proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE,
+ mCurrentInteractiveState);
+ proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE,
+ mEffectiveInteractiveState);
+
+ proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_ON_MS,
+ nowRealtime - mLastScreenOnRealtime);
+ proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS,
+ nowRealtime - mLastScreenOffRealtime);
+
+ mJobCountTracker.dumpProto(proto, JobConcurrencyManagerProto.JOB_COUNT_TRACKER);
+
+ proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL,
+ mLastMemoryTrimLevel);
+
+ proto.end(token);
}
/**
- * This class decides, taking into account {@link #mMaxJobCounts} and how many jos are running /
+ * This class decides, taking into account {@link #mMaxJobCounts} and how mny jos are running /
* pending, how many more job can start.
*
* Extracted for testing and logging.
*/
@VisibleForTesting
static class JobCountTracker {
- private int mConfigNumTotalMaxJobs;
+ private int mConfigNumMaxTotalJobs;
private int mConfigNumMaxBgJobs;
private int mConfigNumMinBgJobs;
@@ -552,7 +553,7 @@
private int mNumActualMaxBgJobs;
void reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs) {
- mConfigNumTotalMaxJobs = numTotalMaxJobs;
+ mConfigNumMaxTotalJobs = numTotalMaxJobs;
mConfigNumMaxBgJobs = numMaxBgJobs;
mConfigNumMinBgJobs = numMinBgJobs;
@@ -607,12 +608,12 @@
// However, if there are FG jobs already running, we have to adjust it.
mNumReservedForBg = Math.min(reservedForBg,
- mConfigNumTotalMaxJobs - mNumRunningFgJobs);
+ mConfigNumMaxTotalJobs - mNumRunningFgJobs);
// Max FG is [total - [number needed for BG jobs]]
// [number needed for BG jobs] is the bigger one of [running BG] or [reserved BG]
final int maxFg =
- mConfigNumTotalMaxJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg);
+ mConfigNumMaxTotalJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg);
// The above maxFg is the theoretical max. If there are less FG jobs, the actual
// max FG will be lower accordingly.
@@ -623,7 +624,7 @@
// Max BG is [total - actual max FG], but cap at [config max BG].
final int maxBg = Math.min(
mConfigNumMaxBgJobs,
- mConfigNumTotalMaxJobs - mNumActualMaxFgJobs);
+ mConfigNumMaxTotalJobs - mNumActualMaxFgJobs);
// If there are less BG jobs than maxBg, then reduce the actual max BG accordingly.
// This isn't needed for the logic to work, but this will give consistent output
@@ -669,12 +670,13 @@
final int totalBg = mNumRunningBgJobs + mNumStartingBgJobs;
return String.format(
"Config={tot=%d bg min/max=%d/%d}"
- + " Running: %d / %d (%d)"
+ + " Running[FG/BG (total)]: %d / %d (%d)"
+ " Pending: %d / %d (%d)"
+ " Actual max: %d%s / %d%s (%d%s)"
+ + " Res BG: %d"
+ " Starting: %d / %d (%d)"
+ " Total: %d%s / %d%s (%d%s)",
- mConfigNumTotalMaxJobs,
+ mConfigNumMaxTotalJobs,
mConfigNumMinBgJobs,
mConfigNumMaxBgJobs,
@@ -684,19 +686,37 @@
mNumPendingFgJobs, mNumPendingBgJobs,
mNumPendingFgJobs + mNumPendingBgJobs,
- mNumActualMaxFgJobs, (totalFg <= mConfigNumTotalMaxJobs) ? "" : "*",
+ mNumActualMaxFgJobs, (totalFg <= mConfigNumMaxTotalJobs) ? "" : "*",
mNumActualMaxBgJobs, (totalBg <= mConfigNumMaxBgJobs) ? "" : "*",
mNumActualMaxFgJobs + mNumActualMaxBgJobs,
- (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumTotalMaxJobs)
+ (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumMaxTotalJobs)
? "" : "*",
+ mNumReservedForBg,
+
mNumStartingFgJobs, mNumStartingBgJobs, mNumStartingFgJobs + mNumStartingBgJobs,
totalFg, (totalFg <= mNumActualMaxFgJobs) ? "" : "*",
totalBg, (totalBg <= mNumActualMaxBgJobs) ? "" : "*",
- totalFg + totalBg, (totalFg + totalBg <= mConfigNumTotalMaxJobs) ? "" : "*"
+ totalFg + totalBg, (totalFg + totalBg <= mConfigNumMaxTotalJobs) ? "" : "*"
);
}
+
+ public void dumpProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_TOTAL_JOBS, mConfigNumMaxTotalJobs);
+ proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_BG_JOBS, mConfigNumMaxBgJobs);
+ proto.write(JobCountTrackerProto.CONFIG_NUM_MIN_BG_JOBS, mConfigNumMinBgJobs);
+
+ proto.write(JobCountTrackerProto.NUM_RUNNING_FG_JOBS, mNumRunningFgJobs);
+ proto.write(JobCountTrackerProto.NUM_RUNNING_BG_JOBS, mNumRunningBgJobs);
+
+ proto.write(JobCountTrackerProto.NUM_PENDING_FG_JOBS, mNumPendingFgJobs);
+ proto.write(JobCountTrackerProto.NUM_PENDING_BG_JOBS, mNumPendingBgJobs);
+
+ proto.end(token);
+ }
}
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index bd12075..7625aaf 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -366,14 +366,20 @@
}
}
- public int getTotalMax() {
+ /** Total number of jobs to run simultaneously. */
+ public int getMaxTotal() {
return mTotal.getValue();
}
+ /** Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. */
public int getMaxBg() {
return mMaxBg.getValue();
}
+ /**
+ * We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any
+ * pending, rather than always running the TOTAL number of FG jobs.
+ */
public int getMinBg() {
return mMinBg.getValue();
}
@@ -384,10 +390,39 @@
mMinBg.dump(pw, prefix);
}
- public void dumpProto(ProtoOutputStream proto, long tagTotal, long tagBg) {
- mTotal.dumpProto(proto, tagTotal);
- mMaxBg.dumpProto(proto, tagBg);
- mMinBg.dumpProto(proto, tagBg);
+ public void dumpProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ mTotal.dumpProto(proto, MaxJobCountsProto.TOTAL_JOBS);
+ mMaxBg.dumpProto(proto, MaxJobCountsProto.MAX_BG);
+ mMinBg.dumpProto(proto, MaxJobCountsProto.MIN_BG);
+ proto.end(token);
+ }
+ }
+
+ /** {@link MaxJobCounts} for each memory trim level. */
+ static class MaxJobCountsPerMemoryTrimLevel {
+ public final MaxJobCounts normal;
+ public final MaxJobCounts moderate;
+ public final MaxJobCounts low;
+ public final MaxJobCounts critical;
+
+ MaxJobCountsPerMemoryTrimLevel(
+ MaxJobCounts normal,
+ MaxJobCounts moderate, MaxJobCounts low,
+ MaxJobCounts critical) {
+ this.normal = normal;
+ this.moderate = moderate;
+ this.low = low;
+ this.critical = critical;
+ }
+
+ public void dumpProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ normal.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.NORMAL);
+ moderate.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.MODERATE);
+ low.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.LOW);
+ critical.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.CRITICAL);
+ proto.end(token);
}
}
@@ -546,45 +581,44 @@
float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;
// Max job counts for screen on / off, for each memory trim level.
- final MaxJobCounts MAX_JOB_COUNTS_ON_NORMAL = new MaxJobCounts(
- 8, "max_job_total_on_normal",
- 6, "max_job_max_bg_on_normal",
- 2, "max_job_min_bg_on_normal");
+ final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_ON =
+ new MaxJobCountsPerMemoryTrimLevel(
+ new MaxJobCounts(
+ 8, "max_job_total_on_normal",
+ 6, "max_job_max_bg_on_normal",
+ 2, "max_job_min_bg_on_normal"),
+ new MaxJobCounts(
+ 8, "max_job_total_on_moderate",
+ 4, "max_job_max_bg_on_moderate",
+ 2, "max_job_min_bg_on_moderate"),
+ new MaxJobCounts(
+ 5, "max_job_total_on_low",
+ 1, "max_job_max_bg_on_low",
+ 1, "max_job_min_bg_on_low"),
+ new MaxJobCounts(
+ 5, "max_job_total_on_critical",
+ 1, "max_job_max_bg_on_critical",
+ 1, "max_job_min_bg_on_critical"));
- final MaxJobCounts MAX_JOB_COUNTS_ON_MODERATE = new MaxJobCounts(
- 8, "max_job_total_on_moderate",
- 4, "max_job_max_bg_on_moderate",
- 2, "max_job_min_bg_on_moderate");
+ final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_OFF =
+ new MaxJobCountsPerMemoryTrimLevel(
+ new MaxJobCounts(
+ 10, "max_job_total_off_normal",
+ 6, "max_job_max_bg_off_normal",
+ 2, "max_job_min_bg_off_normal"),
+ new MaxJobCounts(
+ 10, "max_job_total_off_moderate",
+ 4, "max_job_max_bg_off_moderate",
+ 2, "max_job_min_bg_off_moderate"),
+ new MaxJobCounts(
+ 5, "max_job_total_off_low",
+ 1, "max_job_max_bg_off_low",
+ 1, "max_job_min_bg_off_low"),
+ new MaxJobCounts(
+ 5, "max_job_total_off_critical",
+ 1, "max_job_max_bg_off_critical",
+ 1, "max_job_min_bg_off_critical"));
- final MaxJobCounts MAX_JOB_COUNTS_ON_LOW = new MaxJobCounts(
- 5, "max_job_total_on_low",
- 1, "max_job_max_bg_on_low",
- 1, "max_job_min_bg_on_low");
-
- final MaxJobCounts MAX_JOB_COUNTS_ON_CRITICAL = new MaxJobCounts(
- 5, "max_job_total_on_critical",
- 1, "max_job_max_bg_on_critical",
- 1, "max_job_min_bg_on_critical");
-
- final MaxJobCounts MAX_JOB_COUNTS_OFF_NORMAL = new MaxJobCounts(
- 10, "max_job_total_off_normal",
- 6, "max_job_max_bg_off_normal",
- 2, "max_job_min_bg_off_normal");
-
- final MaxJobCounts MAX_JOB_COUNTS_OFF_MODERATE = new MaxJobCounts(
- 10, "max_job_total_off_moderate",
- 4, "max_job_max_bg_off_moderate",
- 2, "max_job_min_bg_off_moderate");
-
- final MaxJobCounts MAX_JOB_COUNTS_OFF_LOW = new MaxJobCounts(
- 5, "max_job_total_off_low",
- 1, "max_job_max_bg_off_low",
- 1, "max_job_min_bg_off_low");
-
- final MaxJobCounts MAX_JOB_COUNTS_OFF_CRITICAL = new MaxJobCounts(
- 5, "max_job_total_off_critical",
- 1, "max_job_max_bg_off_critical",
- 1, "max_job_min_bg_off_critical");
/** Wait for this long after screen off before increasing the job concurrency. */
final KeyValueListParser.IntValue SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS =
@@ -766,15 +800,15 @@
MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR,
DEFAULT_MODERATE_USE_FACTOR);
- MAX_JOB_COUNTS_ON_NORMAL.parse(mParser);
- MAX_JOB_COUNTS_ON_MODERATE.parse(mParser);
- MAX_JOB_COUNTS_ON_LOW.parse(mParser);
- MAX_JOB_COUNTS_ON_CRITICAL.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_ON.normal.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_ON.moderate.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_ON.low.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_ON.critical.parse(mParser);
- MAX_JOB_COUNTS_OFF_NORMAL.parse(mParser);
- MAX_JOB_COUNTS_OFF_MODERATE.parse(mParser);
- MAX_JOB_COUNTS_OFF_LOW.parse(mParser);
- MAX_JOB_COUNTS_OFF_CRITICAL.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_OFF.normal.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_OFF.moderate.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_OFF.low.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_OFF.critical.parse(mParser);
MAX_STANDARD_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_STANDARD_RESCHEDULE_COUNT,
DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT);
@@ -851,15 +885,17 @@
pw.printPair(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
pw.printPair(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println();
- MAX_JOB_COUNTS_ON_NORMAL.dump(pw, "");
- MAX_JOB_COUNTS_ON_MODERATE.dump(pw, "");
- MAX_JOB_COUNTS_ON_LOW.dump(pw, "");
- MAX_JOB_COUNTS_ON_CRITICAL.dump(pw, "");
+ MAX_JOB_COUNTS_SCREEN_ON.normal.dump(pw, "");
+ MAX_JOB_COUNTS_SCREEN_ON.moderate.dump(pw, "");
+ MAX_JOB_COUNTS_SCREEN_ON.low.dump(pw, "");
+ MAX_JOB_COUNTS_SCREEN_ON.critical.dump(pw, "");
- MAX_JOB_COUNTS_OFF_NORMAL.dump(pw, "");
- MAX_JOB_COUNTS_OFF_MODERATE.dump(pw, "");
- MAX_JOB_COUNTS_OFF_LOW.dump(pw, "");
- MAX_JOB_COUNTS_OFF_CRITICAL.dump(pw, "");
+ MAX_JOB_COUNTS_SCREEN_OFF.normal.dump(pw, "");
+ MAX_JOB_COUNTS_SCREEN_OFF.moderate.dump(pw, "");
+ MAX_JOB_COUNTS_SCREEN_OFF.low.dump(pw, "");
+ MAX_JOB_COUNTS_SCREEN_OFF.critical.dump(pw, "");
+
+ SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dump(pw, "");
pw.printPair(KEY_MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT).println();
pw.printPair(KEY_MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT).println();
@@ -917,7 +953,11 @@
proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR);
proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR);
- // TODO Dump max job counts.
+ MAX_JOB_COUNTS_SCREEN_ON.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_ON);
+ MAX_JOB_COUNTS_SCREEN_OFF.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_OFF);
+
+ SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dumpProto(proto,
+ ConstantsProto.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS);
proto.write(ConstantsProto.MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT);
proto.write(ConstantsProto.MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT);
@@ -3371,8 +3411,10 @@
void dumpInternal(final IndentingPrintWriter pw, int filterUid) {
final int filterUidFinal = UserHandle.getAppId(filterUid);
+ final long now = sSystemClock.millis();
final long nowElapsed = sElapsedRealtimeClock.millis();
final long nowUptime = sUptimeMillisClock.millis();
+
final Predicate<JobStatus> predicate = (js) -> {
return filterUidFinal == -1 || UserHandle.getAppId(js.getUid()) == filterUidFinal
|| UserHandle.getAppId(js.getSourceUid()) == filterUidFinal;
@@ -3548,7 +3590,7 @@
}
pw.println();
- mConcurrencyManager.dumpLocked(pw);
+ mConcurrencyManager.dumpLocked(pw, now, nowElapsed);
pw.println();
pw.print("PersistStats: ");
@@ -3560,6 +3602,7 @@
void dumpInternalProto(final FileDescriptor fd, int filterUid) {
ProtoOutputStream proto = new ProtoOutputStream(fd);
final int filterUidFinal = UserHandle.getAppId(filterUid);
+ final long now = sSystemClock.millis();
final long nowElapsed = sElapsedRealtimeClock.millis();
final long nowUptime = sUptimeMillisClock.millis();
final Predicate<JobStatus> predicate = (js) -> {
@@ -3703,7 +3746,8 @@
proto.write(JobSchedulerServiceDumpProto.IS_READY_TO_ROCK, mReadyToRock);
proto.write(JobSchedulerServiceDumpProto.REPORTED_ACTIVE, mReportedActive);
}
- mConcurrencyManager.dumpProtoLocked(proto);
+ mConcurrencyManager.dumpProtoLocked(proto,
+ JobSchedulerServiceDumpProto.CONCURRENCY_MANAGER, now, nowElapsed);
}
proto.flush();
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 47a5597..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();
@@ -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/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/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/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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b8342cf..9100f6a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -10574,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());
@@ -20672,7 +20670,6 @@
storage.registerListener(mStorageListener);
mInstallerService.systemReady();
- mDexManager.systemReady();
mPackageDexOptimizer.systemReady();
getStorageManagerInternal().addExternalStoragePolicy(
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/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index aaa1874..8d64b81 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -666,6 +666,7 @@
case android.provider.Settings.Secure.ALWAYS_ON_VPN_APP:
case android.provider.Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN:
+ case android.provider.Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST:
// Whitelist system uid (ConnectivityService) and root uid to change always-on vpn
final int appId = UserHandle.getAppId(callingUid);
if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID) {
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 1a2b115..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
@@ -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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 41cab2d..13c4d88 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);
}
@@ -2570,6 +2569,7 @@
}
private static final int[] WINDOW_TYPES_WHERE_HOME_DOESNT_WORK = {
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
};
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index e1a911e..1d82970 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -825,16 +825,16 @@
// like the ANR / app crashed dialogs
return canAddInternalSystemWindow ? 11 : 10;
case TYPE_APPLICATION_OVERLAY:
- return 12;
+ return canAddInternalSystemWindow ? 13 : 12;
case TYPE_DREAM:
// used for Dreams (screensavers with TYPE_DREAM windows)
- return 13;
+ return 14;
case TYPE_INPUT_METHOD:
// on-screen keyboards and other such input method user interfaces go here.
- return 14;
+ return 15;
case TYPE_INPUT_METHOD_DIALOG:
// on-screen keyboards and other such input method user interfaces go here.
- return 15;
+ return 16;
case TYPE_STATUS_BAR:
return 17;
case TYPE_STATUS_BAR_PANEL:
diff --git a/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java
index 055c941..7f2dedb 100644
--- a/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java
+++ b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.app.role.RoleManager;
+import android.content.ComponentName;
import android.content.Context;
import android.os.Debug;
import android.provider.Settings;
@@ -90,6 +91,17 @@
return CollectionUtils.singletonOrEmpty(result);
}
+ case RoleManager.ROLE_ASSISTANT: {
+ String legacyAssistant = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), Settings.Secure.ASSISTANT, userId);
+
+ if (legacyAssistant == null || legacyAssistant.isEmpty()) {
+ return Collections.emptyList();
+ } else {
+ return Collections.singletonList(
+ ComponentName.unflattenFromString(legacyAssistant).getPackageName());
+ }
+ }
default: {
Slog.e(LOG_TAG, "Don't know how to find legacy role holders for " + roleName);
return Collections.emptyList();
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 c0517fd..1c7596b 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -198,6 +198,7 @@
// Make sure to implement LegacyRoleResolutionPolicy#getRoleHolders
// for a given role before adding a migration statement for it here
migrateRoleIfNecessary(RoleManager.ROLE_SMS, userId);
+ migrateRoleIfNecessary(RoleManager.ROLE_ASSISTANT, userId);
// Some vital packages state has changed since last role grant
// Run grants again
diff --git a/services/core/java/com/android/server/rollback/LocalIntentReceiver.java b/services/core/java/com/android/server/rollback/LocalIntentReceiver.java
new file mode 100644
index 0000000..504a349
--- /dev/null
+++ b/services/core/java/com/android/server/rollback/LocalIntentReceiver.java
@@ -0,0 +1,47 @@
+/*
+ * 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.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.IBinder;
+
+import java.util.function.Consumer;
+
+/** {@code IntentSender} implementation for RollbackManager internal use. */
+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) {
+ mConsumer.accept(intent);
+ }
+ };
+
+ public IntentSender getIntentSender() {
+ return new IntentSender((IIntentSender) mLocalSender);
+ }
+}
diff --git a/services/core/java/com/android/server/rollback/RollbackData.java b/services/core/java/com/android/server/rollback/RollbackData.java
index 4015c4f..a4f3064 100644
--- a/services/core/java/com/android/server/rollback/RollbackData.java
+++ b/services/core/java/com/android/server/rollback/RollbackData.java
@@ -49,6 +49,14 @@
*/
public Instant timestamp;
+ /**
+ * 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 7f515bf..8b4c410 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -19,8 +19,6 @@
import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
@@ -36,11 +34,9 @@
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.storage.StorageManager;
@@ -66,7 +62,6 @@
import java.util.Map;
import java.util.Random;
import java.util.Set;
-import java.util.function.Consumer;
/**
* Implementation of service that manages APK level rollbacks.
@@ -116,6 +111,7 @@
private final Context mContext;
private final HandlerThread mHandlerThread;
private final Installer mInstaller;
+ private final RollbackPackageHealthObserver mPackageHealthObserver;
RollbackManagerServiceImpl(Context context) {
mContext = context;
@@ -128,6 +124,8 @@
mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback"));
+ mPackageHealthObserver = new RollbackPackageHealthObserver(mContext);
+
// Kick off loading of the rollback data from strorage in a background
// thread.
// TODO: Consider loading the rollback data directly here instead, to
@@ -285,14 +283,19 @@
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;
}
if (data.rollbackId != rollback.getRollbackId()) {
- sendFailure(statusReceiver, "Rollback for package is out of date");
+ sendFailure(statusReceiver, "Rollback for package is out of date.");
+ return;
+ }
+
+ if (data.inProgress) {
+ sendFailure(statusReceiver, "Rollback for package is already in progress.");
return;
}
@@ -371,27 +374,36 @@
final LocalIntentReceiver receiver = new LocalIntentReceiver(
(Intent result) -> {
- 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;
- }
+ getHandler().post(() -> {
+ // We've now completed the rollback, so we mark it as no longer in
+ // progress.
+ data.inProgress = false;
- addRecentlyExecutedRollback(rollback);
- sendSuccess(statusReceiver);
+ 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;
+ }
- Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
+ addRecentlyExecutedRollback(rollback);
+ sendSuccess(statusReceiver);
- // 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);
+ 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());
} catch (IOException e) {
Log.e(TAG, "Unable to roll back " + targetPackageName, e);
@@ -774,10 +786,15 @@
getHandler().post(() -> {
PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
- // TODO(narayan): Should we make sure we're in the middle of a session commit for a
- // a package with this package name ? Otherwise it's possible we may roll back data
- // for some other downgrade.
- if (getRollbackForPackage(packageName) == null) {
+ 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;
}
@@ -805,26 +822,6 @@
});
}
- 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) {
- getHandler().post(() -> mConsumer.accept(intent));
- }
- };
-
- public IntentSender getIntentSender() {
- return new IntentSender((IIntentSender) mLocalSender);
- }
- }
-
/**
* Gets the version of the package currently installed.
* Returns null if the package is not currently installed.
@@ -891,7 +888,17 @@
ensureRollbackDataLoadedLocked();
mAvailableRollbacks.add(data);
}
-
+ // TODO(zezeozue): Provide API to explicitly start observing instead
+ // of doing this for all rollbacks. If we do this for all rollbacks,
+ // should document in PackageInstaller.SessionParams#setEnableRollback
+ // After enabling and commiting any rollback, observe packages and
+ // prepare to rollback if packages crashes too frequently.
+ List<String> packages = new ArrayList<>();
+ for (int i = 0; i < data.packages.size(); i++) {
+ packages.add(data.packages.get(i).getPackageName());
+ }
+ mPackageHealthObserver.startObservingHealth(packages,
+ ROLLBACK_LIFETIME_DURATION_MILLIS);
scheduleExpiration(ROLLBACK_LIFETIME_DURATION_MILLIS);
} catch (IOException e) {
Log.e(TAG, "Unable to enable rollback", e);
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
new file mode 100644
index 0000000..1f2f1cc
--- /dev/null
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -0,0 +1,94 @@
+/*
+ * 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.rollback;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInstaller;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import com.android.server.PackageWatchdog;
+import com.android.server.PackageWatchdog.PackageHealthObserver;
+
+import java.util.List;
+
+/**
+ * {@code PackageHealthObserver} for {@code RollbackManagerService}.
+ *
+ * @hide
+ */
+public final class RollbackPackageHealthObserver implements PackageHealthObserver {
+ private static final String TAG = "RollbackPackageHealthObserver";
+ private static final String NAME = "rollback-observer";
+ private Context mContext;
+ private Handler mHandler;
+
+ RollbackPackageHealthObserver(Context context) {
+ mContext = context;
+ HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
+ handlerThread.start();
+ mHandler = handlerThread.getThreadHandler();
+ PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
+ }
+
+ @Override
+ public boolean onHealthCheckFailed(String packageName) {
+ RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
+ RollbackInfo rollback = rollbackManager.getAvailableRollback(packageName);
+ if (rollback != null) {
+ // TODO(zezeozue): Only rollback if rollback version == failed package version
+ mHandler.post(() -> executeRollback(rollbackManager, rollback));
+ return true;
+ }
+ // Don't handle the notification, no rollbacks available
+ return false;
+ }
+
+ /**
+ * Start observing health of {@code packages} for {@code durationMs}.
+ * This may cause {@code packages} to be rolled back if they crash too freqeuntly.
+ */
+ public void startObservingHealth(List<String> packages, long durationMs) {
+ PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
+ }
+
+ private void executeRollback(RollbackManager manager, RollbackInfo rollback) {
+ // TODO(zezeozue): Log initiated metrics
+ LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver((Intent result) -> {
+ mHandler.post(() -> {
+ int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ // TODO(zezeozue); Log success metrics
+ // Rolledback successfully, no action required by other observers
+ } else {
+ // TODO(zezeozue); Log failure metrics
+ // Rollback failed other observers should have a shot
+ }
+ });
+ });
+ manager.executeRollback(rollback, rollbackReceiver.getIntentSender());
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+}
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 40664fe..acede7d 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -1705,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.
*/
@@ -1867,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/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/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d36e545..3a077b8 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -747,12 +747,23 @@
abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
callingPid, resolvedType, aInfo.applicationInfo);
- // not sure if we need to create START_ABORTED_BACKGROUND so for now piggybacking
- // on START_ABORTED
+ boolean abortBackgroundStart = false;
if (!abort) {
- abort |= shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage,
- realCallingUid, callerApp, originatingPendingIntent,
+ abortBackgroundStart = shouldAbortBackgroundActivityStart(callingUid, callingPid,
+ callingPackage, realCallingUid, callerApp, originatingPendingIntent,
allowBackgroundActivityStart, intent);
+ abort |= (abortBackgroundStart && !mService.isBackgroundActivityStartsEnabled());
+ // TODO: remove this toast after feature development is done
+ if (abortBackgroundStart) {
+ final String toastMsg = abort
+ ? "Background activity start from " + callingPackage
+ + " blocked. See go/q-bg-block."
+ : "This background activity start from " + callingPackage
+ + " will be blocked in future Q builds. See go/q-bg-block.";
+ mService.mUiHandler.post(() -> {
+ Toast.makeText(mService.mContext, toastMsg, Toast.LENGTH_LONG).show();
+ });
+ }
}
// Merge the two options bundles, while realCallerOptions takes precedence.
@@ -798,8 +809,6 @@
// We pretend to the caller that it was really started, but
// they will just get a cancel result.
ActivityOptions.abort(checkedOptions);
- maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp,
- null /*r*/, originatingPendingIntent, true /*abortedStart*/);
return START_ABORTED;
}
@@ -892,8 +901,11 @@
mService.onStartActivitySetDidAppSwitch();
mController.doPendingActivityLaunches(false);
- maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, r,
- originatingPendingIntent, false /*abortedStart*/);
+ // maybe log to TRON, but only if we haven't already in shouldAbortBackgroundActivityStart()
+ if (!abortBackgroundStart) {
+ maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, r,
+ originatingPendingIntent, false /*abortedStart*/);
+ }
return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags,
true /* doResume */, checkedOptions, inTask, outActivity);
@@ -903,11 +915,9 @@
final String callingPackage, int realCallingUid, WindowProcessController callerApp,
PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart,
Intent intent) {
- if (mService.isBackgroundActivityStartsEnabled()) {
- return false;
- }
// don't abort for the most important UIDs
- if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID) {
+ if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID
+ || callingUid == Process.NFC_UID) {
return false;
}
// don't abort if the callerApp has any visible activity
@@ -915,14 +925,15 @@
return false;
}
// don't abort if the callingUid is in the foreground or is a persistent system process
- final boolean isCallingUidForeground = isUidForeground(callingUid);
+ final boolean isCallingUidForeground = mService.isUidForeground(callingUid);
final boolean isCallingUidPersistentSystemProcess = isUidPersistentSystemProcess(
callingUid);
if (isCallingUidForeground || isCallingUidPersistentSystemProcess) {
return false;
}
// take realCallingUid into consideration
- final boolean isRealCallingUidForeground = isUidForeground(realCallingUid);
+ final boolean isRealCallingUidForeground = mService.isUidForeground(
+ realCallingUid);
final boolean isRealCallingUidPersistentSystemProcess = isUidPersistentSystemProcess(
realCallingUid);
if (realCallingUid != callingUid) {
@@ -949,8 +960,8 @@
if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
return false;
}
- // anything that has fallen through will currently be aborted
- Slog.w(TAG, "Blocking background activity start [callingPackage: " + callingPackage
+ // anything that has fallen through would currently be aborted
+ Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
+ "; callingUid: " + callingUid
+ "; isCallingUidForeground: " + isCallingUidForeground
+ "; isCallingUidPersistentSystemProcess: " + isCallingUidPersistentSystemProcess
@@ -962,21 +973,11 @@
+ "; isBgStartWhitelisted: " + allowBackgroundActivityStart
+ "; intent: " + intent
+ "]");
- // TODO: remove this toast after feature development is done
- mService.mUiHandler.post(() -> {
- Toast.makeText(mService.mContext,
- "Blocking background activity start for " + callingPackage,
- Toast.LENGTH_SHORT).show();
- });
+ maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp,
+ null /*r*/, originatingPendingIntent, true /*abortedStart*/);
return true;
}
- /** Returns true if uid has a visible window or its process is in a top state. */
- private boolean isUidForeground(int uid) {
- return (mService.getUidStateLocked(uid) == ActivityManager.PROCESS_STATE_TOP)
- || mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid);
- }
-
/** Returns true if uid is in a persistent state. */
private boolean isUidPersistentSystemProcess(int uid) {
return (mService.getUidStateLocked(uid) <= ActivityManager.PROCESS_STATE_PERSISTENT_UI);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 67b00b2..1a5e6a1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -489,4 +489,7 @@
*/
public abstract ActivityManager.TaskSnapshot getTaskSnapshot(int taskId,
boolean reducedResolution);
+
+ /** Returns true if uid has a visible window or its process is in a top state. */
+ public abstract boolean isUidForeground(int uid);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 2affa97..e5a66cb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5632,6 +5632,11 @@
return mActiveUids.get(uid, PROCESS_STATE_NONEXISTENT);
}
+ boolean isUidForeground(int uid) {
+ return (getUidStateLocked(uid) == ActivityManager.PROCESS_STATE_TOP)
+ || mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid);
+ }
+
/**
* @return whitelist tag for a uid from mPendingTempWhitelist, null if not currently on
* the whitelist
@@ -6222,30 +6227,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
@@ -7044,5 +7046,12 @@
return ActivityTaskManagerService.this.getTaskSnapshot(taskId, reducedResolution);
}
}
+
+ @Override
+ public boolean isUidForeground(int uid) {
+ synchronized (mGlobalLock) {
+ return ActivityTaskManagerService.this.isUidForeground(uid);
+ }
+ }
}
}
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 750c5ca..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;
@@ -33,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;
@@ -97,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;
@@ -117,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;
@@ -261,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;
@@ -272,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;
@@ -810,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;
@@ -1528,6 +1545,7 @@
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
final int prevWinMode = getWindowingMode();
+ mTmpPrevBounds.set(getBounds());
super.onConfigurationChanged(newParentConfig);
final int winMode = getWindowingMode();
@@ -1559,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) {
@@ -2242,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) {
@@ -2260,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();
@@ -2294,6 +2402,10 @@
if (adapter.getShowWallpaper()) {
mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
+ if (thumbnailAdapter != null) {
+ mThumbnail.startAnimation(
+ getPendingTransaction(), thumbnailAdapter, !isVisible());
+ }
}
} else {
cancelAnimation();
@@ -2429,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);
}
@@ -2487,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);
@@ -2518,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() {
@@ -2835,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 8fefd35..8f976e7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -250,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;
@@ -2454,6 +2455,7 @@
// Clear all transitions & screen frozen states when removing display.
mOpeningApps.clear();
mClosingApps.clear();
+ mChangingApps.clear();
mUnknownAppVisibilityController.clear();
mAppTransition.removeAppTransitionTimeoutCallbacks();
handleAnimatingStoppedAndTransition();
@@ -3284,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);
@@ -3292,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, " ");
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 6d3c693..4006332 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -58,6 +58,7 @@
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
@@ -2029,7 +2030,8 @@
of.set(displayFrames.mRestricted);
df.set(displayFrames.mRestricted);
pf.set(displayFrames.mRestricted);
- } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) {
+ } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT
+ || type == TYPE_APPLICATION_OVERLAY) {
// These dialogs are stable to interim decor changes.
cf.set(displayFrames.mStable);
of.set(displayFrames.mStable);
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/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/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/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/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/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 5267e7e..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;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c6679a9..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;
@@ -857,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;
@@ -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);
}
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/com_android_server_lights_LightsService.cpp b/services/core/jni/com_android_server_lights_LightsService.cpp
index c90113f..26f6d74 100644
--- a/services/core/jni/com_android_server_lights_LightsService.cpp
+++ b/services/core/jni/com_android_server_lights_LightsService.cpp
@@ -39,37 +39,7 @@
template<typename T>
using Return = ::android::hardware::Return<T>;
-class LightHal {
-private:
- static sp<ILight> sLight;
- static bool sLightInit;
-
- LightHal() {}
-
-public:
- static void disassociate() {
- sLightInit = false;
- sLight = nullptr;
- }
-
- static sp<ILight> associate() {
- if ((sLight == nullptr && !sLightInit) ||
- (sLight != nullptr && !sLight->ping().isOk())) {
- // will return the hal if it exists the first time.
- sLight = ILight::getService();
- sLightInit = true;
-
- if (sLight == nullptr) {
- ALOGE("Unable to get ILight interface.");
- }
- }
-
- return sLight;
- }
-};
-
-sp<ILight> LightHal::sLight = nullptr;
-bool LightHal::sLightInit = false;
+static bool sLightSupported = true;
static bool validate(jint light, jint flash, jint brightness) {
bool valid = true;
@@ -134,7 +104,6 @@
const LightState &state) {
if (!ret.isOk()) {
ALOGE("Failed to issue set light command.");
- LightHal::disassociate();
return;
}
@@ -164,13 +133,11 @@
jint offMS,
jint brightnessMode) {
- if (!validate(light, flashMode, brightnessMode)) {
+ if (!sLightSupported) {
return;
}
- sp<ILight> hal = LightHal::associate();
-
- if (hal == nullptr) {
+ if (!validate(light, flashMode, brightnessMode)) {
return;
}
@@ -180,6 +147,11 @@
{
android::base::Timer t;
+ sp<ILight> hal = ILight::getService();
+ if (hal == nullptr) {
+ sLightSupported = false;
+ return;
+ }
Return<Status> ret = hal->setLight(type, state);
processReturn(ret, type, state);
if (t.duration() > 50ms) ALOGD("Excessive delay setting light");
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 54053a8..9088820 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -70,6 +70,7 @@
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
+import static android.app.admin.DevicePolicyManager.WIPE_SILENTLY;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
@@ -77,14 +78,10 @@
import static android.provider.Telephony.Carriers.ENFORCE_KEY;
import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent
- .PROVISIONING_ENTRY_POINT_ADB;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
-import static com.android.server.devicepolicy.TransferOwnershipMetadataManager
- .ADMIN_TYPE_DEVICE_OWNER;
-import static com.android.server.devicepolicy.TransferOwnershipMetadataManager
- .ADMIN_TYPE_PROFILE_OWNER;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER;
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -949,8 +946,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 +1069,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 +1342,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 +1542,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 +1760,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);
+ }
}
}
@@ -1916,7 +1919,11 @@
}
AlarmManager getAlarmManager() {
- return (AlarmManager) mContext.getSystemService(AlarmManager.class);
+ return mContext.getSystemService(AlarmManager.class);
+ }
+
+ ConnectivityManager getConnectivityManager() {
+ return mContext.getSystemService(ConnectivityManager.class);
}
IWindowManager getIWindowManager() {
@@ -6299,7 +6306,8 @@
* @throws UnsupportedOperationException if the package does not support being set as always-on.
*/
@Override
- public boolean setAlwaysOnVpnPackage(ComponentName admin, String vpnPackage, boolean lockdown)
+ public boolean setAlwaysOnVpnPackage(ComponentName admin, String vpnPackage, boolean lockdown,
+ List<String> lockdownWhitelist)
throws SecurityException {
enforceProfileOrDeviceOwner(admin);
@@ -6307,11 +6315,23 @@
final long token = mInjector.binderClearCallingIdentity();
try {
if (vpnPackage != null && !isPackageInstalledForUser(vpnPackage, userId)) {
- return false;
+ Slog.w(LOG_TAG, "Non-existent VPN package specified: " + vpnPackage);
+ throw new ServiceSpecificException(
+ DevicePolicyManager.ERROR_VPN_PACKAGE_NOT_FOUND, vpnPackage);
}
- ConnectivityManager connectivityManager = (ConnectivityManager)
- mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- if (!connectivityManager.setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdown)) {
+
+ if (vpnPackage != null && lockdown && lockdownWhitelist != null) {
+ for (String packageName : lockdownWhitelist) {
+ if (!isPackageInstalledForUser(packageName, userId)) {
+ Slog.w(LOG_TAG, "Non-existent package in VPN whitelist: " + packageName);
+ throw new ServiceSpecificException(
+ DevicePolicyManager.ERROR_VPN_PACKAGE_NOT_FOUND, packageName);
+ }
+ }
+ }
+ // If some package is uninstalled after the check above, it will be ignored by CM.
+ if (!mInjector.getConnectivityManager().setAlwaysOnVpnPackageForUser(
+ userId, vpnPackage, lockdown, lockdownWhitelist)) {
throw new UnsupportedOperationException();
}
DevicePolicyEventLogger
@@ -6328,16 +6348,40 @@
}
@Override
- public String getAlwaysOnVpnPackage(ComponentName admin)
+ public String getAlwaysOnVpnPackage(ComponentName admin) throws SecurityException {
+ enforceProfileOrDeviceOwner(admin);
+
+ final int userId = mInjector.userHandleGetCallingUserId();
+ final long token = mInjector.binderClearCallingIdentity();
+ try {
+ return mInjector.getConnectivityManager().getAlwaysOnVpnPackageForUser(userId);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean isAlwaysOnVpnLockdownEnabled(ComponentName admin) throws SecurityException {
+ enforceProfileOrDeviceOwner(admin);
+
+ final int userId = mInjector.userHandleGetCallingUserId();
+ final long token = mInjector.binderClearCallingIdentity();
+ try {
+ return mInjector.getConnectivityManager().isVpnLockdownEnabled(userId);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public List<String> getAlwaysOnVpnLockdownWhitelist(ComponentName admin)
throws SecurityException {
enforceProfileOrDeviceOwner(admin);
final int userId = mInjector.userHandleGetCallingUserId();
final long token = mInjector.binderClearCallingIdentity();
- try{
- ConnectivityManager connectivityManager = (ConnectivityManager)
- mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- return connectivityManager.getAlwaysOnVpnPackageForUser(userId);
+ try {
+ return mInjector.getConnectivityManager().getVpnLockdownWhitelist(userId);
} finally {
mInjector.binderRestoreCallingIdentity(token);
}
@@ -6362,7 +6406,7 @@
}
}
- private void forceWipeUser(int userId, String wipeReasonForUser) {
+ private void forceWipeUser(int userId, String wipeReasonForUser, boolean wipeSilently) {
boolean success = false;
try {
IActivityManager am = mInjector.getIActivityManager();
@@ -6373,7 +6417,7 @@
success = mUserManagerInternal.removeUserEvenWhenDisallowed(userId);
if (!success) {
Slog.w(LOG_TAG, "Couldn't remove user " + userId);
- } else if (isManagedProfile(userId) && !TextUtils.isEmpty(wipeReasonForUser)) {
+ } else if (isManagedProfile(userId) && !wipeSilently) {
sendWipeProfileNotification(wipeReasonForUser);
}
} catch (RemoteException re) {
@@ -6388,6 +6432,7 @@
if (!mHasFeature) {
return;
}
+ Preconditions.checkStringNotEmpty(wipeReasonForUser, "wipeReasonForUser is null or empty");
enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId());
final ActiveAdmin admin;
@@ -6447,7 +6492,7 @@
internalReason,
/*wipeEuicc=*/ (flags & WIPE_EUICC) != 0);
} else {
- forceWipeUser(userId, wipeReasonForUser);
+ forceWipeUser(userId, wipeReasonForUser, (flags & WIPE_SILENTLY) != 0);
}
} finally {
mInjector.binderRestoreCallingIdentity(ident);
@@ -6809,9 +6854,7 @@
enforceDeviceOwner(who);
long token = mInjector.binderClearCallingIdentity();
try {
- ConnectivityManager connectivityManager = (ConnectivityManager)
- mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- connectivityManager.setGlobalProxy(proxyInfo);
+ mInjector.getConnectivityManager().setGlobalProxy(proxyInfo);
} finally {
mInjector.binderRestoreCallingIdentity(token);
}
@@ -13988,55 +14031,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();
@@ -14046,7 +14061,7 @@
synchronized (getLockObject()) {
final ActiveAdmin admin = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- return new ArrayList<String>(admin.mCrossProfileCalendarPackages);
+ return admin.mCrossProfileCalendarPackages;
}
}
@@ -14062,6 +14077,9 @@
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
if (admin != null) {
+ if (admin.mCrossProfileCalendarPackages == null) {
+ return true;
+ }
return admin.mCrossProfileCalendarPackages.contains(packageName);
}
}
@@ -14077,7 +14095,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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 65eaf554..5861368 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1561,6 +1561,12 @@
traceEnd();
}
+ // Grants default permissions and defines roles
+ traceBeginAndSlog("StartRoleManagerService");
+ mSystemServiceManager.startService(new RoleManagerService(
+ mSystemContext, new LegacyRoleResolutionPolicy(mSystemContext)));
+ traceEnd();
+
// We need to always start this service, regardless of whether the
// FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
// of initializing various settings. It will internally modify its behavior
@@ -2011,12 +2017,6 @@
}
traceEnd();
- // Grants default permissions and defines roles
- traceBeginAndSlog("StartRoleManagerService");
- mSystemServiceManager.startService(new RoleManagerService(
- mSystemContext, new LegacyRoleResolutionPolicy(mSystemContext)));
- traceEnd();
-
// No dependency on Webview preparation in system server. But this should
// be completed before allowing 3rd party
final String WEBVIEW_PREPARATION = "WebViewFactoryPreparation";
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/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/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/am/AppCompactorTest.java b/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java
new file mode 100644
index 0000000..1a231cf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java
@@ -0,0 +1,379 @@
+/*
+ * 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.am;
+
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_1;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_2;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_1;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_2;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_3;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_4;
+import static android.provider.DeviceConfig.ActivityManager.KEY_USE_COMPACTION;
+
+import static com.android.server.am.ActivityManagerService.Injector;
+import static com.android.server.am.AppCompactor.compactActionIntToString;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.provider.DeviceConfig;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.appop.AppOpsService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link AppCompactor}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:AppCompactorTest
+ */
+@RunWith(AndroidJUnit4.class)
+public final class AppCompactorTest {
+
+ @Mock private AppOpsService mAppOpsService;
+ private AppCompactor mCompactorUnderTest;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private CountDownLatch mCountDown;
+
+ private static void clearDeviceConfig() throws IOException {
+ UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ uiDevice.executeShellCommand(
+ "device_config delete activity_manager " + KEY_USE_COMPACTION);
+ uiDevice.executeShellCommand(
+ "device_config delete activity_manager " + KEY_COMPACT_ACTION_1);
+ uiDevice.executeShellCommand(
+ "device_config delete activity_manager " + KEY_COMPACT_ACTION_2);
+ uiDevice.executeShellCommand(
+ "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_1);
+ uiDevice.executeShellCommand(
+ "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_2);
+ uiDevice.executeShellCommand(
+ "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_3);
+ uiDevice.executeShellCommand(
+ "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_4);
+ }
+
+ @Before
+ public void setUp() throws IOException {
+ MockitoAnnotations.initMocks(this);
+ clearDeviceConfig();
+ mHandlerThread = new HandlerThread("");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ ActivityManagerService ams = new ActivityManagerService(new TestInjector());
+ mCompactorUnderTest = new AppCompactor(ams,
+ new AppCompactor.PropertyChangedCallbackForTest() {
+ @Override
+ public void onPropertyChanged() {
+ if (mCountDown != null) {
+ mCountDown.countDown();
+ }
+ }
+ });
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ mHandlerThread.quit();
+ mCountDown = null;
+ clearDeviceConfig();
+ }
+
+ @Test
+ public void init_setsDefaults() {
+ mCompactorUnderTest.init();
+ assertThat(mCompactorUnderTest.useCompaction(),
+ is(mCompactorUnderTest.DEFAULT_USE_COMPACTION));
+ assertThat(mCompactorUnderTest.mCompactActionSome, is(
+ compactActionIntToString(mCompactorUnderTest.DEFAULT_COMPACT_ACTION_1)));
+ assertThat(mCompactorUnderTest.mCompactActionFull, is(
+ compactActionIntToString(mCompactorUnderTest.DEFAULT_COMPACT_ACTION_2)));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4));
+ }
+
+ @Test
+ public void init_withDeviceConfigSetsParameters() {
+ // When the DeviceConfig already has a flag value stored (note this test will need to
+ // change if the default value changes from false).
+ assertThat(mCompactorUnderTest.DEFAULT_USE_COMPACTION, is(false));
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_USE_COMPACTION, "true", false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_1,
+ Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 3) + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_2,
+ Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 3) + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_1,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_2,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_3,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_4,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+
+ // Then calling init will read and set that flag.
+ mCompactorUnderTest.init();
+ assertThat(mCompactorUnderTest.useCompaction(), is(true));
+ assertThat(mCompactorUnderTest.mCompactionThread.isAlive(), is(true));
+
+ assertThat(mCompactorUnderTest.mCompactActionSome,
+ is(compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 3) + 1)));
+ assertThat(mCompactorUnderTest.mCompactActionFull,
+ is(compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 3) + 1)));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1));
+ }
+
+ @Test
+ public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException {
+ assertThat(mCompactorUnderTest.useCompaction(),
+ is(mCompactorUnderTest.DEFAULT_USE_COMPACTION));
+ // When we call init and change some the flag value...
+ mCompactorUnderTest.init();
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_USE_COMPACTION, "true", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+ // Then that new flag value is updated in the implementation.
+ assertThat(mCompactorUnderTest.useCompaction(), is(true));
+ assertThat(mCompactorUnderTest.mCompactionThread.isAlive(), is(true));
+
+ // And again, setting the flag the other way.
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_USE_COMPACTION, "false", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+ assertThat(mCompactorUnderTest.useCompaction(), is(false));
+ }
+
+ @Test
+ public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+ assertThat(mCompactorUnderTest.useCompaction(),
+ is(mCompactorUnderTest.DEFAULT_USE_COMPACTION));
+ mCompactorUnderTest.init();
+
+ // When we push an invalid flag value...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_USE_COMPACTION, "foobar", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+ // Then we set the default.
+ assertThat(mCompactorUnderTest.useCompaction(), is(AppCompactor.DEFAULT_USE_COMPACTION));
+ }
+
+ @Test
+ public void compactAction_listensToDeviceConfigChanges() throws InterruptedException {
+ mCompactorUnderTest.init();
+
+ // When we override new values for the compaction action with reasonable values...
+
+ // There are three possible values for compactAction[Some|Full].
+ for (int i = 1; i < 4; i++) {
+ mCountDown = new CountDownLatch(2);
+ int expectedSome = (mCompactorUnderTest.DEFAULT_COMPACT_ACTION_1 + i) % 3 + 1;
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false);
+ int expectedFull = (mCompactorUnderTest.DEFAULT_COMPACT_ACTION_2 + i) % 3 + 1;
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+ // Then the updates are reflected in the flags.
+ assertThat(mCompactorUnderTest.mCompactActionSome,
+ is(compactActionIntToString(expectedSome)));
+ assertThat(mCompactorUnderTest.mCompactActionFull,
+ is(compactActionIntToString(expectedFull)));
+ }
+ }
+
+ @Test
+ public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+ mCompactorUnderTest.init();
+
+ // When we override new values for the compaction action with bad values ...
+ mCountDown = new CountDownLatch(2);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_1, "foo", false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_2, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+ // Then the default values are reflected in the flag
+ assertThat(mCompactorUnderTest.mCompactActionSome,
+ is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1)));
+ assertThat(mCompactorUnderTest.mCompactActionFull,
+ is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2)));
+
+ mCountDown = new CountDownLatch(2);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_1, "", false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_2, "", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+ assertThat(mCompactorUnderTest.mCompactActionSome,
+ is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1)));
+ assertThat(mCompactorUnderTest.mCompactActionFull,
+ is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2)));
+ }
+
+ @Test
+ public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException {
+ mCompactorUnderTest.init();
+
+ // When we override new reasonable throttle values after init...
+ mCountDown = new CountDownLatch(4);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_1,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_2,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_3,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_4,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+ // Then those flags values are reflected in the compactor.
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1));
+ }
+
+ @Test
+ public void compactThrottle_listensToDeviceConfigChangesBadValues()
+ throws IOException, InterruptedException {
+ mCompactorUnderTest.init();
+
+ // When one of the throttles is overridden with a bad value...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_1, "foo", false);
+ // Then all the throttles have the defaults set.
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4));
+ clearDeviceConfig();
+
+ // Repeat for each of the throttle keys.
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_2, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4));
+ clearDeviceConfig();
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_3, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4));
+ clearDeviceConfig();
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_4, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4));
+ }
+
+ private class TestInjector extends Injector {
+ @Override
+ public AppOpsService getAppOpsService(File file, Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandler;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
index ac4a5fe..a3f36b7 100644
--- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
@@ -24,11 +24,15 @@
import static junit.framework.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
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.Manifest;
import android.annotation.UserIdInt;
import android.app.backup.BackupManager;
import android.app.backup.IBackupManagerMonitor;
@@ -39,21 +43,21 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
import android.util.SparseArray;
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.util.test.FakeSettingsProvider;
-
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -99,10 +103,6 @@
@Mock
private Context mContextMock;
@Mock
- private File mSuppressFileMock;
- @Mock
- private File mSuppressFileParentMock;
- @Mock
private IBinder mAgentMock;
@Mock
private ParcelFileDescriptor mParcelFileDescriptorMock;
@@ -114,95 +114,53 @@
private IBackupManagerMonitor mBackupManagerMonitorMock;
@Mock
private PrintWriter mPrintWriterMock;
+ @Mock
+ private UserManager mUserManagerMock;
+ @Mock
+ private UserInfo mUserInfoMock;
private FileDescriptor mFileDescriptorStub = new FileDescriptor();
private TrampolineTestable mTrampoline;
- private MockContentResolver mContentResolver;
+ private File mTestDir;
+ private File mSuppressFile;
+ private File mActivatedFile;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mUserId = NON_USER_SYSTEM;
+ mUserId = UserHandle.USER_SYSTEM;
SparseArray<UserBackupManagerService> serviceUsers = new SparseArray<>();
- serviceUsers.append(UserHandle.SYSTEM.getIdentifier(), mUserBackupManagerService);
+ serviceUsers.append(UserHandle.USER_SYSTEM, mUserBackupManagerService);
serviceUsers.append(NON_USER_SYSTEM, mUserBackupManagerService);
when(mBackupManagerServiceMock.getServiceUsers()).thenReturn(serviceUsers);
+ when(mUserManagerMock.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(mUserInfoMock);
+ when(mUserManagerMock.getUserInfo(NON_USER_SYSTEM)).thenReturn(mUserInfoMock);
+
TrampolineTestable.sBackupManagerServiceMock = mBackupManagerServiceMock;
TrampolineTestable.sCallingUserId = UserHandle.USER_SYSTEM;
TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
TrampolineTestable.sBackupDisabled = false;
+ TrampolineTestable.sUserManagerMock = mUserManagerMock;
- when(mSuppressFileMock.getParentFile()).thenReturn(mSuppressFileParentMock);
+ mTestDir = InstrumentationRegistry.getContext().getFilesDir();
+ mTestDir.mkdirs();
+
+ mSuppressFile = new File(mTestDir, "suppress");
+ TrampolineTestable.sSuppressFile = mSuppressFile;
+
+ mActivatedFile = new File(mTestDir, "activate-" + NON_USER_SYSTEM);
+ TrampolineTestable.sActivatedFiles.append(NON_USER_SYSTEM, mActivatedFile);
mTrampoline = new TrampolineTestable(mContextMock);
-
- mContentResolver = new MockContentResolver();
- mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- when(mContextMock.getContentResolver()).thenReturn(mContentResolver);
}
- @Test
- public void unlockUser_whenMultiUserSettingDisabled_callsBackupManagerServiceForSystemUser() {
- Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
- mTrampoline.initializeService();
-
- mTrampoline.unlockUser(UserHandle.USER_SYSTEM);
-
- verify(mBackupManagerServiceMock).startServiceForUser(UserHandle.USER_SYSTEM);
- }
-
- @Test
- public void unlockUser_whenMultiUserSettingDisabled_isIgnoredForNonSystemUser() {
- Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
- mTrampoline.initializeService();
-
- mTrampoline.unlockUser(NON_USER_SYSTEM);
-
- verify(mBackupManagerServiceMock, never()).startServiceForUser(NON_USER_SYSTEM);
- }
-
- @Test
- public void unlockUser_whenMultiUserSettingEnabled_callsBackupManagerServiceForNonSystemUser() {
- Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1);
- mTrampoline.initializeService();
-
- mTrampoline.unlockUser(NON_USER_SYSTEM);
-
- verify(mBackupManagerServiceMock).startServiceForUser(NON_USER_SYSTEM);
- }
-
- @Test
- public void stopUser_whenMultiUserSettingDisabled_callsBackupManagerServiceForSystemUser() {
- Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
- mTrampoline.initializeService();
-
- mTrampoline.stopUser(UserHandle.USER_SYSTEM);
-
- verify(mBackupManagerServiceMock).stopServiceForUser(UserHandle.USER_SYSTEM);
- }
-
- @Test
- public void stopUser_whenMultiUserSettingDisabled_isIgnoredForNonSystemUser() {
- Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
- mTrampoline.initializeService();
-
- mTrampoline.stopUser(NON_USER_SYSTEM);
-
- verify(mBackupManagerServiceMock, never()).stopServiceForUser(NON_USER_SYSTEM);
- }
-
- @Test
- public void stopUser_whenMultiUserSettingEnabled_callsBackupManagerServiceForNonSystemUser() {
- Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1);
-
- mTrampoline.initializeService();
-
- mTrampoline.stopUser(NON_USER_SYSTEM);
-
- verify(mBackupManagerServiceMock).stopServiceForUser(NON_USER_SYSTEM);
+ @After
+ public void tearDown() throws Exception {
+ mSuppressFile.delete();
+ mActivatedFile.delete();
}
@Test
@@ -222,18 +180,6 @@
assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
- // Verify that BackupManagerService is not initialized if suppress file exists.
- @Test
- public void initializeService_suppressFileExists_nonInitialized() throws Exception {
- TrampolineTestable trampoline = new TrampolineTestable(mContextMock);
- trampoline.createBackupSuppressFileForUser(UserHandle.USER_SYSTEM);
-
-
- trampoline.initializeService();
-
- assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
- }
-
@Test
public void initializeService_doesNotStartServiceForUsers() {
mTrampoline.initializeService();
@@ -247,15 +193,55 @@
}
@Test
- public void isBackupServiceActive_forNonSysUser_whenSysUserIsDeactivated_returnsFalse() {
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ public void isBackupServiceActive_forSystemUser_returnsTrueWhenActivated() throws Exception {
+ mTrampoline.initializeService();
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+
+ assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void isBackupServiceActive_forSystemUser_returnsFalseWhenDeactivated() throws Exception {
+ mTrampoline.initializeService();
mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+ assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenSystemUserDeactivated()
+ throws Exception {
+ mTrampoline.initializeService();
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+
assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
}
@Test
- public void setBackupServiceActive_callerSystemUid_serviceCreated() {
+ public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenNonSystemUserDeactivated()
+ throws Exception {
+ mTrampoline.initializeService();
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ // Don't activate non-system user.
+
+ assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ }
+
+ @Test
+ public void
+ isBackupServiceActive_forNonSystemUser_returnsTrueWhenSystemAndNonSystemUserActivated()
+ throws Exception {
+ mTrampoline.initializeService();
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+
+ assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ }
+
+ @Test
+ public void setBackupServiceActive_forSystemUserAndCallerSystemUid_serviceCreated() {
+ mTrampoline.initializeService();
TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
@@ -264,7 +250,8 @@
}
@Test
- public void setBackupServiceActive_callerRootUid_serviceCreated() {
+ public void setBackupServiceActive_forSystemUserAndCallerRootUid_serviceCreated() {
+ mTrampoline.initializeService();
TrampolineTestable.sCallingUid = Process.ROOT_UID;
mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
@@ -273,7 +260,8 @@
}
@Test
- public void setBackupServiceActive_callerNonRootNonSystem_securityExceptionThrown() {
+ public void setBackupServiceActive_forSystemUserAndCallerNonRootNonSystem_throws() {
+ mTrampoline.initializeService();
TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID;
try {
@@ -281,14 +269,77 @@
fail();
} catch (SecurityException expected) {
}
+ }
- assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ @Test
+ public void setBackupServiceActive_forManagedProfileAndCallerSystemUid_serviceCreated() {
+ when(mUserInfoMock.isManagedProfile()).thenReturn(true);
+ mTrampoline.initializeService();
+ TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
+
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+
+ assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ }
+
+ @Test
+ public void setBackupServiceActive_forManagedProfileAndCallerRootUid_serviceCreated() {
+ when(mUserInfoMock.isManagedProfile()).thenReturn(true);
+ mTrampoline.initializeService();
+ TrampolineTestable.sCallingUid = Process.ROOT_UID;
+
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+
+ assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ }
+
+ @Test
+ public void setBackupServiceActive_forManagedProfileAndCallerNonRootNonSystem_throws() {
+ when(mUserInfoMock.isManagedProfile()).thenReturn(true);
+ mTrampoline.initializeService();
+ TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID;
+
+ try {
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ fail();
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
+ public void setBackupServiceActive_forNonSystemUserAndCallerWithoutBackupPermission_throws() {
+ doThrow(new SecurityException())
+ .when(mContextMock)
+ .enforceCallingOrSelfPermission(eq(Manifest.permission.BACKUP), anyString());
+ mTrampoline.initializeService();
+
+ try {
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ fail();
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
+ public void setBackupServiceActive_forNonSystemUserAndCallerWithoutUserPermission_throws() {
+ doThrow(new SecurityException())
+ .when(mContextMock)
+ .enforceCallingOrSelfPermission(
+ eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyString());
+ mTrampoline.initializeService();
+
+ try {
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ fail();
+ } catch (SecurityException expected) {
+ }
}
@Test
public void setBackupServiceActive_backupDisabled_ignored() {
TrampolineTestable.sBackupDisabled = true;
TrampolineTestable trampoline = new TrampolineTestable(mContextMock);
+ trampoline.initializeService();
trampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
@@ -296,14 +347,8 @@
}
@Test
- public void setBackupServiceActive_nonUserSystem_ignored() {
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
-
- assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
- }
-
- @Test
public void setBackupServiceActive_alreadyActive_ignored() {
+ mTrampoline.initializeService();
mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
assertEquals(1, mTrampoline.getCreateServiceCallsCount());
@@ -315,6 +360,7 @@
@Test
public void setBackupServiceActive_makeNonActive_alreadyNonActive_ignored() {
+ mTrampoline.initializeService();
mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
@@ -323,6 +369,7 @@
@Test
public void setBackupServiceActive_makeActive_serviceCreatedAndSuppressFileDeleted() {
+ mTrampoline.initializeService();
mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
@@ -349,6 +396,21 @@
}
@Test
+ public void setBackupServiceActive_forOneNonSystemUser_doesNotActivateForAllNonSystemUsers() {
+ mTrampoline.initializeService();
+ int otherUser = NON_USER_SYSTEM + 1;
+ File activateFile = new File(mTestDir, "activate-" + otherUser);
+ TrampolineTestable.sActivatedFiles.append(otherUser, activateFile);
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+
+ assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ assertFalse(mTrampoline.isBackupServiceActive(otherUser));
+ activateFile.delete();
+ }
+
+ @Test
public void dataChanged_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.dataChanged(PACKAGE_NAME);
verifyNoMoreInteractions(mBackupManagerServiceMock);
@@ -1123,7 +1185,7 @@
@Test
public void requestBackup_forwardedToCallingUserId() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
- when(mBackupManagerServiceMock.requestBackup(NON_USER_SYSTEM, PACKAGE_NAMES,
+ when(mBackupManagerServiceMock.requestBackup(mUserId, PACKAGE_NAMES,
mBackupObserverMock, mBackupManagerMonitorMock, 123)).thenReturn(456);
mTrampoline.initializeService();
@@ -1227,50 +1289,33 @@
static int sCallingUserId = -1;
static int sCallingUid = -1;
static BackupManagerService sBackupManagerServiceMock = null;
+ static File sSuppressFile = null;
+ static SparseArray<File> sActivatedFiles = new SparseArray<>();
+ static UserManager sUserManagerMock = null;
private int mCreateServiceCallsCount = 0;
- private SparseArray<FakeFile> mSuppressFiles = new SparseArray<>();
-
- private static class FakeFile extends File {
- private boolean mExists;
-
- FakeFile(String pathname) {
- super(pathname);
- }
-
- @Override
- public boolean exists() {
- return mExists;
- }
-
- @Override
- public boolean delete() {
- mExists = false;
- return true;
- }
-
- @Override
- public boolean createNewFile() throws IOException {
- mExists = true;
- return true;
- }
- }
TrampolineTestable(Context context) {
super(context);
}
@Override
+ protected UserManager getUserManager() {
+ return sUserManagerMock;
+ }
+
+ @Override
public boolean isBackupDisabled() {
return sBackupDisabled;
}
@Override
- public File getSuppressFileForUser(int userId) {
- if (mSuppressFiles.get(userId) == null) {
- FakeFile file = new FakeFile(Integer.toString(userId));
- mSuppressFiles.append(userId, file);
- }
- return mSuppressFiles.get(userId);
+ protected File getSuppressFileForSystemUser() {
+ return sSuppressFile;
+ }
+
+ @Override
+ protected File getActivatedFileForNonSystemUser(int userId) {
+ return sActivatedFiles.get(userId);
}
protected int binderGetCallingUserId() {
@@ -1289,11 +1334,6 @@
}
@Override
- protected void createBackupSuppressFileForUser(int userId) throws IOException {
- getSuppressFileForUser(userId).createNewFile();
- }
-
- @Override
protected void postToHandler(Runnable runnable) {
runnable.run();
}
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 0813e6fa..535198b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5239,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/job/MaxJobCountsTest.java b/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
index 01199a3..0219f22 100644
--- a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
@@ -17,15 +17,15 @@
import android.util.KeyValueListParser;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.server.job.JobSchedulerService.MaxJobCounts;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MaxJobCountsTest {
@@ -43,13 +43,14 @@
counts.parse(parser);
- Assert.assertEquals(expectedTotal, counts.getTotalMax());
+ Assert.assertEquals(expectedTotal, counts.getMaxTotal());
Assert.assertEquals(expectedMaxBg, counts.getMaxBg());
Assert.assertEquals(expectedMinBg, counts.getMinBg());
}
@Test
public void test() {
+ // Tests with various combinations.
check("", /*default*/ 5, 1, 0, /*expected*/ 5, 1, 0);
check("", /*default*/ 5, 0, 0, /*expected*/ 5, 1, 0);
check("", /*default*/ 0, 0, 0, /*expected*/ 1, 1, 0);
@@ -58,7 +59,11 @@
check("", /*default*/ 6, 5, 6, /*expected*/ 6, 5, 5);
check("", /*default*/ 4, 5, 6, /*expected*/ 4, 4, 3);
check("", /*default*/ 5, 1, 1, /*expected*/ 5, 1, 1);
+ check("", /*default*/ 15, 15, 15, /*expected*/ 15, 15, 14);
+ check("", /*default*/ 16, 16, 16, /*expected*/ 16, 16, 15);
+ check("", /*default*/ 20, 20, 20, /*expected*/ 16, 16, 15);
+ // Test for overriding with a setting string.
check("total=5,maxbg=4,minbg=3", /*default*/ 9, 9, 9, /*expected*/ 5, 4, 3);
check("total=5", /*default*/ 9, 9, 9, /*expected*/ 5, 5, 4);
check("maxbg=4", /*default*/ 9, 9, 9, /*expected*/ 9, 4, 4);
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/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/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index c0f9b80..6222923 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -245,8 +245,8 @@
}
@Override
- void logSmartSuggestionsVisible(NotificationRecord r) {
- super.logSmartSuggestionsVisible(r);
+ void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) {
+ super.logSmartSuggestionsVisible(r, notificationLocation);
countLogSmartSuggestionsVisible++;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index a381023..056568a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -549,8 +549,7 @@
verify(mActivityMetricsLogger, times(1)).logActivityStart(any(), any(), any(),
eq(FAKE_CALLING_UID), eq(FAKE_CALLING_PACKAGE), anyInt(), anyBoolean(),
eq(FAKE_REAL_CALLING_UID), anyInt(), anyBoolean(), anyInt(),
- eq(ActivityBuilder.getDefaultComponent().getPackageName()), anyInt(), anyBoolean(),
- any(), eq(false));
+ any(), anyInt(), anyBoolean(), any(), eq(false));
}
/**
@@ -599,6 +598,10 @@
Process.SYSTEM_UID, false, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
false, false, false);
+ runAndVerifyBackgroundActivityStartsSubtest("disallowed_nfcUid_notAborted", false,
+ Process.NFC_UID, false, PROCESS_STATE_TOP + 1,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ false, false, false);
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_callingUidHasVisibleWindow_notAborted", false,
UNIMPORTANT_UID, true, PROCESS_STATE_TOP + 1,
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/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/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 6ad698b..85939d4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -855,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;
@@ -1346,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);
}
@@ -1447,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
@@ -1652,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/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/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 8c82cc8..697469a 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -939,11 +939,7 @@
runOrAddOperation(new Operation(
// always execute:
() -> {
- // Don't remove the callback if multiple triggers are allowed or
- // if this event was triggered by a getModelState request
- if (!mRecognitionConfig.allowMultipleTriggers
- && event.status
- != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+ if (!mRecognitionConfig.allowMultipleTriggers) {
// Unregister this remoteService once op is done
synchronized (mCallbacksLock) {
mCallbacks.remove(mPuuid.getUuid());
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 613c4ff..718f2d3 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -17,16 +17,18 @@
package com.android.server.voiceinteraction;
import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
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.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
@@ -62,8 +64,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,10 +78,12 @@
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;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* SystemService that publishes an IVoiceInteractionManagerService.
@@ -201,6 +206,7 @@
VoiceInteractionManagerServiceStub() {
mEnableService = shouldEnableService(mContext);
+ new RoleObserver(mContext.getMainExecutor());
}
// TODO: VI Make sure the caller is the current user or profile
@@ -1205,6 +1211,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) {
@@ -1218,6 +1275,106 @@
getActiveServiceComponentName());
}
+ class RoleObserver implements OnRoleHoldersChangedListener {
+ private PackageManager mPm = mContext.getPackageManager();
+ private RoleManager mRm = mContext.getSystemService(RoleManager.class);
+
+ RoleObserver(@NonNull @CallbackExecutor Executor executor) {
+ mRm.addOnRoleHoldersChangedListenerAsUser(executor, this, UserHandle.ALL);
+ }
+
+ private @NonNull String getDefaultRecognizer(@NonNull UserHandle user) {
+ ResolveInfo resolveInfo = mPm.resolveServiceAsUser(
+ new Intent(RecognitionService.SERVICE_INTERFACE),
+ PackageManager.GET_META_DATA, user.getIdentifier());
+
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Log.w(TAG, "Unable to resolve default voice recognition service.");
+ return "";
+ }
+
+ return new ComponentName(resolveInfo.serviceInfo.packageName,
+ resolveInfo.serviceInfo.name).flattenToShortString();
+ }
+
+ /**
+ * Convert the assistant-role holder into settings. The rest of the system uses the
+ * settings.
+ *
+ * @param roleName the name of the role whose holders are changed
+ * @param user the user for this role holder change
+ */
+ @Override
+ public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+ if (!roleName.equals(RoleManager.ROLE_ASSISTANT)) {
+ return;
+ }
+
+ List<String> roleHolders = mRm.getRoleHoldersAsUser(roleName, user);
+
+ if (roleHolders.isEmpty()) {
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.ASSISTANT, "");
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.VOICE_INTERACTION_SERVICE, "");
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.VOICE_RECOGNITION_SERVICE, getDefaultRecognizer(user));
+ } else {
+ // Assistant is singleton role
+ String pkg = roleHolders.get(0);
+
+ // Try to set role holder as VoiceInteractionService
+ List<ResolveInfo> services = mPm.queryIntentServicesAsUser(
+ new Intent(VoiceInteractionService.SERVICE_INTERFACE).setPackage(pkg),
+ PackageManager.GET_META_DATA, user.getIdentifier());
+
+ for (ResolveInfo resolveInfo : services) {
+ ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+
+ VoiceInteractionServiceInfo voiceInteractionServiceInfo =
+ new VoiceInteractionServiceInfo(mPm, serviceInfo);
+ if (!voiceInteractionServiceInfo.getSupportsAssist()) {
+ continue;
+ }
+
+ String serviceComponentName = serviceInfo.getComponentName()
+ .flattenToShortString();
+
+ String serviceRecognizerName = new ComponentName(pkg,
+ voiceInteractionServiceInfo.getRecognitionService())
+ .flattenToShortString();
+
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.ASSISTANT, serviceComponentName);
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.VOICE_INTERACTION_SERVICE, serviceComponentName);
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.VOICE_RECOGNITION_SERVICE, serviceRecognizerName);
+
+ return;
+ }
+
+ // If no service could be found try to set assist activity
+ final List<ResolveInfo> activities = mPm.queryIntentActivitiesAsUser(
+ new Intent(Intent.ACTION_ASSIST).setPackage(pkg),
+ PackageManager.MATCH_DEFAULT_ONLY, user.getIdentifier());
+
+ for (ResolveInfo resolveInfo : activities) {
+ ActivityInfo activityInfo = resolveInfo.activityInfo;
+
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.ASSISTANT,
+ activityInfo.getComponentName().flattenToShortString());
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.VOICE_INTERACTION_SERVICE, "");
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.VOICE_RECOGNITION_SERVICE,
+ getDefaultRecognizer(user));
+ }
+ }
+ }
+ }
+
class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler) {
super(handler);
diff --git a/startop/OWNERS b/startop/OWNERS
index 762cd8e..5cf9582 100644
--- a/startop/OWNERS
+++ b/startop/OWNERS
@@ -2,5 +2,5 @@
chriswailes@google.com
eholk@google.com
iam@google.com
-sehr@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/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 0fe5e08..12a5344 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1970,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/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index e1d5c17..5030f90 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -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 a33b44c..349880d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -608,11 +608,41 @@
public static final String KEY_CARRIER_PROMOTE_WFC_ON_CALL_FAIL_BOOL =
"carrier_promote_wfc_on_call_fail_bool";
- /** Flag specifying whether provisioning is required for VOLTE. */
+ /**
+ * Flag specifying whether provisioning is required for VoLTE, Video Telephony, and WiFi
+ * Calling.
+ */
public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
= "carrier_volte_provisioning_required_bool";
/**
+ * Flag indicating whether or not the IMS MmTel UT capability requires carrier provisioning
+ * before it can be set as enabled.
+ *
+ * If true, the UT capability will be set to false for the newly loaded subscription
+ * and will require the carrier provisioning app to set the persistent provisioning result.
+ * If false, the platform will not wait for provisioning status updates for the UT capability
+ * and enable the UT over IMS capability for the subscription when the subscription is loaded.
+ *
+ * The default value for this key is {@code false}.
+ */
+ public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL =
+ "carrier_ut_provisioning_required_bool";
+
+ /**
+ * Flag indicating whether or not the carrier supports Supplementary Services over the UT
+ * interface for this subscription.
+ *
+ * If true, the device will use Supplementary Services over UT when provisioned (see
+ * {@link #KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL}). If false, this device will fallback to
+ * circuit switch for supplementary services and will disable this capability for IMS entirely.
+ *
+ * The default value for this key is {@code true}.
+ */
+ public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL =
+ "carrier_supports_ss_over_ut_bool";
+
+ /**
* Flag specifying if WFC provisioning depends on VoLTE provisioning.
*
* {@code false}: default value; honor actual WFC provisioning state.
@@ -2575,6 +2605,8 @@
sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT, 2);
sDefaults.putBoolean(KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false);
+ sDefaults.putBoolean(KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL, false);
+ sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL, true);
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/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 9414abd..5b2e635 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -86,9 +86,7 @@
/**
* Prefer registering for IMS over IWLAN if possible if WiFi signal quality is high enough.
- * @hide
*/
- @SystemApi
public static final int WIFI_MODE_WIFI_PREFERRED = 2;
/**
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index d37198a..086a765 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -21,13 +21,17 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.WorkerThread;
import android.content.Context;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.ims.aidl.IImsConfigCallback;
+import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.stub.ImsConfigImplBase;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
import com.android.internal.telephony.ITelephony;
@@ -38,13 +42,68 @@
* to changes in these configurations.
*
* Note: IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning
- * applications and may vary.
+ * applications and may vary. For compatibility purposes, the first 100 integer values used in
+ * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys
+ * previously defined in the Android framework. Some common constants have been defined in this
+ * class to make integrating with other system apps easier. USE WITH CARE!
+ *
+ * To avoid collisions, please use String based configurations when possible:
+ * {@link #setProvisioningStringValue(int, String)} and {@link #getProvisioningStringValue(int)}.
* @hide
*/
@SystemApi
public class ProvisioningManager {
/**
+ * The query from {@link #getProvisioningStringValue(int)} has resulted in an unspecified error.
+ */
+ public static final String STRING_QUERY_RESULT_ERROR_GENERIC =
+ "STRING_QUERY_RESULT_ERROR_GENERIC";
+
+ /**
+ * The query from {@link #getProvisioningStringValue(int)} has resulted in an error because the
+ * ImsService implementation was not ready for provisioning queries.
+ */
+ public static final String STRING_QUERY_RESULT_ERROR_NOT_READY =
+ "STRING_QUERY_RESULT_ERROR_NOT_READY";
+
+ /**
+ * The integer result of provisioning for the queried key is disabled.
+ */
+ public static final int PROVISIONING_VALUE_DISABLED = 0;
+
+ /**
+ * The integer result of provisioning for the queried key is enabled.
+ */
+ public static final int PROVISIONING_VALUE_ENABLED = 1;
+
+
+ /**
+ * Override the user-defined WiFi Roaming enabled setting for this subscription, defined in
+ * {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning
+ * the subscription for WiFi Calling.
+ *
+ * @see #getProvisioningIntValue(int)
+ * @see #setProvisioningIntValue(int, int)
+ */
+ public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26;
+
+ /**
+ * Override the user-defined WiFi mode for this subscription, defined in
+ * {@link SubscriptionManager#WFC_MODE_CONTENT_URI}, for the purposes of provisioning
+ * this subscription for WiFi Calling.
+ *
+ * Valid values for this key are:
+ * {@link ImsMmTelManager#WIFI_MODE_WIFI_ONLY},
+ * {@link ImsMmTelManager#WIFI_MODE_CELLULAR_PREFERRED}, or
+ * {@link ImsMmTelManager#WIFI_MODE_WIFI_PREFERRED}.
+ *
+ * @see #getProvisioningIntValue(int)
+ * @see #setProvisioningIntValue(int, int)
+ */
+ public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27;
+
+ /**
* Callback for IMS provisioning changes.
*/
public static class Callback {
@@ -180,10 +239,15 @@
/**
* Query for the integer value associated with the provided key.
+ *
+ * This operation is blocking and should not be performed on the UI thread.
+ *
* @param key An integer that represents the provisioning key, which is defined by the OEM.
- * @return an integer value for the provided key.
+ * @return an integer value for the provided key, or
+ * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist.
* @throws IllegalArgumentException if the key provided was invalid.
*/
+ @WorkerThread
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public int getProvisioningIntValue(int key) {
try {
@@ -195,10 +259,16 @@
/**
* Query for the String value associated with the provided key.
- * @param key An integer that represents the provisioning key, which is defined by the OEM.
- * @return a String value for the provided key, or {@code null} if the key doesn't exist.
+ *
+ * This operation is blocking and should not be performed on the UI thread.
+ *
+ * @param key A String that represents the provisioning key, which is defined by the OEM.
+ * @return a String value for the provided key, {@code null} if the key doesn't exist, or one
+ * of the following error codes: {@link #STRING_QUERY_RESULT_ERROR_GENERIC},
+ * {@link #STRING_QUERY_RESULT_ERROR_NOT_READY}.
* @throws IllegalArgumentException if the key provided was invalid.
*/
+ @WorkerThread
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getProvisioningStringValue(int key) {
try {
@@ -210,10 +280,16 @@
/**
* Set the integer value associated with the provided key.
+ *
+ * This operation is blocking and should not be performed on the UI thread.
+ *
+ * Use {@link #setProvisioningStringValue(int, String)} with proper namespacing (to be defined
+ * per OEM or carrier) when possible instead to avoid key collision if needed.
* @param key An integer that represents the provisioning key, which is defined by the OEM.
* @param value a integer value for the provided key.
* @return the result of setting the configuration value.
*/
+ @WorkerThread
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public @ImsConfigImplBase.SetConfigResult int setProvisioningIntValue(int key, int value) {
try {
@@ -226,10 +302,14 @@
/**
* Set the String value associated with the provided key.
*
- * @param key An integer that represents the provisioning key, which is defined by the OEM.
+ * This operation is blocking and should not be performed on the UI thread.
+ *
+ * @param key A String that represents the provisioning key, which is defined by the OEM and
+ * should be appropriately namespaced to avoid collision.
* @param value a String value for the provided key.
* @return the result of setting the configuration value.
*/
+ @WorkerThread
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public @ImsConfigImplBase.SetConfigResult int setProvisioningStringValue(int key,
String value) {
@@ -240,6 +320,55 @@
}
}
+ /**
+ * Set the provisioning status for the IMS MmTel capability using the specified subscription.
+ *
+ * Provisioning may or may not be required, depending on the carrier configuration. If
+ * provisioning is not required for the carrier associated with this subscription or the device
+ * does not support the capability/technology combination specified, this operation will be a
+ * no-op.
+ *
+ * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL
+ * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
+ * @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise.
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public void setProvisioningStatusForCapability(
+ @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
+ @ImsRegistrationImplBase.ImsRegistrationTech int tech, boolean isProvisioned) {
+ try {
+ getITelephony().setImsProvisioningStatusForCapability(mSubId, capability, tech,
+ isProvisioned);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Get the provisioning status for the IMS MmTel capability specified.
+ *
+ * If provisioning is not required for the queried
+ * {@link MmTelFeature.MmTelCapabilities.MmTelCapability} and
+ * {@link ImsRegistrationImplBase.ImsRegistrationTech} combination specified, this method will
+ * always return {@code true}.
+ *
+ * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL
+ * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
+ * @return true if the device is provisioned for the capability or does not require
+ * provisioning, false if the capability does require provisioning and has not been
+ * provisioned yet.
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean getProvisioningStatusForCapability(
+ @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
+ @ImsRegistrationImplBase.ImsRegistrationTech int tech) {
+ try {
+ return getITelephony().getImsProvisioningStatusForCapability(mSubId, capability, tech);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
private static SubscriptionManager getSubscriptionManager(Context context) {
SubscriptionManager manager = context.getSystemService(SubscriptionManager.class);
if (manager == null) {
diff --git a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
index 7c793a5..1ee8563 100644
--- a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
+++ b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
@@ -97,6 +97,13 @@
public @ImsRegistrationImplBase.ImsRegistrationTech int getRadioTech() {
return radioTech;
}
+
+ @Override
+ public String toString() {
+ return "CapabilityPair{"
+ + "mCapability=" + mCapability
+ + ", radioTech=" + radioTech + '}';
+ }
}
// Pair contains <radio tech, mCapability>
@@ -212,6 +219,13 @@
}
}
+ @Override
+ public String toString() {
+ return "CapabilityChangeRequest{"
+ + "mCapabilitiesToEnable=" + mCapabilitiesToEnable
+ + ", mCapabilitiesToDisable=" + mCapabilitiesToDisable + '}';
+ }
+
/**
* @hide
*/
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
index 71a2174..4fc6a19 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -277,12 +277,14 @@
* Wi-Fi calling roaming status.
* Value is in Integer format. ON (1), OFF(0).
*/
- public static final int VOICE_OVER_WIFI_ROAMING = 26;
+ public static final int VOICE_OVER_WIFI_ROAMING =
+ ProvisioningManager.KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE;
/**
* Wi-Fi calling modem - WfcModeFeatureValueConstants.
* Value is in Integer format.
*/
- public static final int VOICE_OVER_WIFI_MODE = 27;
+ public static final int VOICE_OVER_WIFI_MODE =
+ ProvisioningManager.KEY_VOICE_OVER_WIFI_MODE_OVERRIDE;
/**
* VOLTE Status for voice over wifi status of Enabled (1), or Disabled (0).
* Value is in Integer format.
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 9cc173c..8237d39 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1765,6 +1765,24 @@
void unregisterImsProvisioningChangedCallback(int subId, IImsConfigCallback callback);
/**
+ * Set the provisioning status for the IMS MmTel capability using the specified subscription.
+ */
+ void setImsProvisioningStatusForCapability(int subId, int capability, int tech,
+ boolean isProvisioned);
+
+ /**
+ * Get the provisioning status for the IMS MmTel capability specified.
+ */
+ boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech);
+
+ /** Is the capability and tech flagged as provisioned in the cache */
+ boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech);
+
+ /** Set the provisioning for the capability and tech in the cache */
+ void cacheMmTelCapabilityProvisioning(int subId, int capability, int tech,
+ boolean isProvisioned);
+
+ /**
* Return an integer containing the provisioning value for the specified provisioning key.
*/
int getImsProvisioningInt(int subId, int key);
diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
index 0edc002..c76d153 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
@@ -284,10 +284,6 @@
*/
private static boolean reportAccessDeniedToReadIdentifiers(Context context, int subId, int pid,
int uid, String callingPackage, String message) {
- // If the device identifier check is enabled then enforce the new access requirements for
- // both 1P and 3P apps.
- boolean enableDeviceIdentifierCheck = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED, 0) == 1;
// Check if the application is a 3P app; if so then a separate setting is required to relax
// the check to begin flagging problems with 3P apps early.
boolean relax3PDeviceIdentifierCheck = Settings.Global.getInt(context.getContentResolver(),
@@ -300,6 +296,11 @@
context.getContentResolver(),
Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED, 0) == 1;
boolean isNonPrivApp = false;
+ // Similar to above support relaxing the check for privileged apps while still enforcing it
+ // for non-privileged and 3P apps.
+ boolean relaxPrivDeviceIdentifierCheck = Settings.Global.getInt(
+ context.getContentResolver(),
+ Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED, 0) == 1;
ApplicationInfo callingPackageInfo = null;
try {
callingPackageInfo = context.getPackageManager().getApplicationInfo(callingPackage, 0);
@@ -315,37 +316,28 @@
Log.e(LOG_TAG, "Exception caught obtaining package info for package " + callingPackage,
e);
}
- Log.wtf(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message
- + ":is3PApp=" + is3PApp + ":isNonPrivApp=" + isNonPrivApp);
- // The new Q restrictions for device identifier access will be enforced if any of the
- // following are true:
- // - The PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED setting has been set.
- // - The app requesting a device identifier is not a preloaded app (3P), and the
- // PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED setting has not been set.
- // - The app requesting a device identifier is a preloaded app but is not a privileged app,
- // and the PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED setting has not been set.
- if (enableDeviceIdentifierCheck
+ // The new Q restrictions for device identifier access will be enforced for all apps with
+ // settings to individually disable the new restrictions for privileged, preloaded
+ // non-privileged, and 3P apps.
+ if ((!is3PApp && !isNonPrivApp && !relaxPrivDeviceIdentifierCheck)
|| (is3PApp && !relax3PDeviceIdentifierCheck)
|| (isNonPrivApp && !relaxNonPrivDeviceIdentifierCheck)) {
- boolean targetQBehaviorDisabled = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED, 0) == 0;
- if (callingPackage != null) {
- // if the target SDK is pre-Q or the target Q behavior is disabled then check if
- // the calling package would have previously had access to device identifiers.
- if (callingPackageInfo != null && (
- callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q
- || targetQBehaviorDisabled)) {
- if (context.checkPermission(
- android.Manifest.permission.READ_PHONE_STATE,
- pid,
- uid) == PackageManager.PERMISSION_GRANTED) {
- return false;
- }
- if (SubscriptionManager.isValidSubscriptionId(subId)
- && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, uid)
- == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
- return false;
- }
+ Log.wtf(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message
+ + ":is3PApp=" + is3PApp + ":isNonPrivApp=" + isNonPrivApp);
+ // if the target SDK is pre-Q then check if the calling package would have previously
+ // had access to device identifiers.
+ if (callingPackageInfo != null && (
+ callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q)) {
+ if (context.checkPermission(
+ android.Manifest.permission.READ_PHONE_STATE,
+ pid,
+ uid) == PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ if (SubscriptionManager.isValidSubscriptionId(subId)
+ && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, uid)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ return false;
}
}
throw new SecurityException(message + ": The user " + uid
diff --git a/tests/RollbackTest/Android.mk b/tests/RollbackTest/Android.mk
index 34aa258..780bb24 100644
--- a/tests/RollbackTest/Android.mk
+++ b/tests/RollbackTest/Android.mk
@@ -36,6 +36,17 @@
include $(BUILD_PACKAGE)
ROLLBACK_TEST_APP_AV2 := $(LOCAL_INSTALLED_MODULE)
+# RollbackTestAppACrashingV2.apk
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, TestApp/src)
+LOCAL_MANIFEST_FILE := TestApp/ACrashingV2.xml
+LOCAL_PACKAGE_NAME := RollbackTestAppACrashingV2
+include $(BUILD_PACKAGE)
+ROLLBACK_TEST_APP_A_CRASHING_V2 := $(LOCAL_INSTALLED_MODULE)
+
# RollbackTestAppBv1.apk
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
@@ -68,6 +79,7 @@
LOCAL_JAVA_RESOURCE_FILES := \
$(ROLLBACK_TEST_APP_AV1) \
$(ROLLBACK_TEST_APP_AV2) \
+ $(ROLLBACK_TEST_APP_A_CRASHING_V2) \
$(ROLLBACK_TEST_APP_BV1) \
$(ROLLBACK_TEST_APP_BV2)
LOCAL_SDK_VERSION := system_current
@@ -77,5 +89,6 @@
# Clean up local variables
ROLLBACK_TEST_APP_AV1 :=
ROLLBACK_TEST_APP_AV2 :=
+ROLLBACK_TEST_APP_A_CRASHING_V2 :=
ROLLBACK_TEST_APP_BV1 :=
ROLLBACK_TEST_APP_BV2 :=
diff --git a/tests/RollbackTest/TestApp/ACrashingV2.xml b/tests/RollbackTest/TestApp/ACrashingV2.xml
new file mode 100644
index 0000000..5708d23
--- /dev/null
+++ b/tests/RollbackTest/TestApp/ACrashingV2.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.rollback.testapp.A"
+ android:versionCode="2"
+ android:versionName="2.0" >
+
+
+ <uses-sdk android:minSdkVersion="19" />
+
+ <application android:label="Rollback Test App A v2">
+ <meta-data android:name="version" android:value="2" />
+ <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
+ android:exported="true" />
+ <activity android:name="com.android.tests.rollback.testapp.CrashingMainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java
new file mode 100644
index 0000000..02a439b
--- /dev/null
+++ b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java
@@ -0,0 +1,33 @@
+/*
+ * 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.tests.rollback.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * A crashing test app for testing apk rollback support.
+ */
+public class CrashingMainActivity extends Activity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ throw new RuntimeException("Intended force crash");
+ }
+}
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 9d67cea..13ac4f0 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -38,6 +38,7 @@
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;
@@ -57,6 +58,7 @@
private static final String TEST_APP_A = "com.android.tests.rollback.testapp.A";
private static final String TEST_APP_B = "com.android.tests.rollback.testapp.B";
+ private static final String INSTRUMENTED_APP = "com.android.tests.rollback";
/**
* Test basic rollbacks.
@@ -663,4 +665,63 @@
assertEquals(packageName, info.getVersionRolledBackTo().getPackageName());
assertEquals(versionRolledBackTo, info.getVersionRolledBackTo().getLongVersionCode());
}
+
+ // TODO(zezeozue): Stop ignoring after fixing race between rolling back and testing version
+ /**
+ * Test bad update automatic rollback.
+ */
+ @Ignore("Flaky")
+ @Test
+ public void testBadUpdateRollback() throws Exception {
+ try {
+ RollbackTestUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.MANAGE_ROLLBACKS);
+ RollbackManager rm = RollbackTestUtils.getRollbackManager();
+
+ // Prep installation of the test apps.
+ RollbackTestUtils.uninstall(TEST_APP_A);
+ RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
+ RollbackTestUtils.install("RollbackTestAppACrashingV2.apk", true);
+ assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+
+ RollbackTestUtils.uninstall(TEST_APP_B);
+ RollbackTestUtils.install("RollbackTestAppBv1.apk", false);
+ RollbackTestUtils.install("RollbackTestAppBv2.apk", true);
+ assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+
+ // Both test apps should now be available for rollback, and the
+ // targetPackage returned for rollback should be correct.
+ // TODO: See if there is a way to remove this race condition
+ // between when the app is installed and when the rollback
+ // is made available.
+ Thread.sleep(1000);
+ RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
+ assertNotNull(rollbackA);
+ assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName());
+
+ RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
+ assertNotNull(rollbackB);
+ assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName());
+
+ // Start apps PackageWatchdog#TRIGGER_FAILURE_COUNT times so TEST_APP_A crashes
+ for (int i = 0; i < 5; i++) {
+ RollbackTestUtils.launchPackage(TEST_APP_A);
+ Thread.sleep(1000);
+ }
+ Thread.sleep(1000);
+
+ // TEST_APP_A is automatically rolled back by the RollbackPackageHealthObserver
+ assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ // Instrumented app is still the package installer
+ Context context = InstrumentationRegistry.getContext();
+ String installer = context.getPackageManager().getInstallerPackageName(TEST_APP_A);
+ assertEquals(INSTRUMENTED_APP, installer);
+ // TEST_APP_B is untouched
+ assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+ } finally {
+ RollbackTestUtils.dropShellPermissionIdentity();
+ }
+ }
}
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
index fbc3d8f..edb1355 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
@@ -135,6 +135,17 @@
assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
}
+ /** Launches {@code packageName} with {@link Intent#ACTION_MAIN}. */
+ static void launchPackage(String packageName)
+ throws InterruptedException, IOException {
+ Context context = InstrumentationRegistry.getContext();
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setPackage(packageName);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ context.startActivity(intent);
+ }
+
/**
* Installs the apks with the given resource names as an atomic set.
*
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/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index dda4481..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;
}
@@ -3548,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) {
@@ -3695,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);
@@ -4914,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/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 0b74d87..5b17224 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -246,17 +246,17 @@
assertFalse(vpn.getLockdown());
// Set always-on without lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList()));
assertTrue(vpn.getAlwaysOn());
assertFalse(vpn.getLockdown());
// Set always-on with lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList()));
assertTrue(vpn.getAlwaysOn());
assertTrue(vpn.getLockdown());
// Remove always-on configuration.
- assertTrue(vpn.setAlwaysOnPackage(null, false));
+ assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
assertFalse(vpn.getAlwaysOn());
assertFalse(vpn.getLockdown());
}
@@ -270,11 +270,11 @@
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
// Set always-on without lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null));
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
// Set always-on with lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -283,7 +283,7 @@
assertUnblocked(vpn, user.start + PKG_UIDS[1]);
// Switch to another app.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -297,6 +297,87 @@
}
@Test
+ public void testLockdownWhitelist() throws Exception {
+ final Vpn vpn = createVpn(primaryUser.id);
+ final UidRange user = UidRange.createForUser(primaryUser.id);
+
+ // Set always-on with lockdown and whitelist app PKGS[2] from lockdown.
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[2])));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
+ new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
+ new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
+ }));
+ assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
+ assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
+
+ // Change whitelisted app to PKGS[3].
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[3])));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
+ new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
+ }));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
+ new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1),
+ new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
+ }));
+ assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[2]);
+ assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[3]);
+
+ // Change the VPN app.
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[3])));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
+ new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
+ new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1)
+ }));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
+ new UidRange(user.start, user.start + PKG_UIDS[0] - 1),
+ new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1)
+ }));
+ assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
+ assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
+
+ // Remove the whitelist.
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
+ new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1),
+ new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
+ }));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
+ new UidRange(user.start + PKG_UIDS[0] + 1, user.stop),
+ }));
+ assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2],
+ user.start + PKG_UIDS[3]);
+ assertUnblocked(vpn, user.start + PKG_UIDS[0]);
+
+ // Add the whitelist.
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[1])));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
+ new UidRange(user.start + PKG_UIDS[0] + 1, user.stop)
+ }));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
+ new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
+ new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
+ }));
+ assertBlocked(vpn, user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
+ assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]);
+
+ // Try whitelisting a package with a comma, should be rejected.
+ assertFalse(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList("a.b,c.d")));
+
+ // Pass a non-existent packages in the whitelist, they (and only they) should be ignored.
+ // Whitelisted package should change from PGKS[1] to PKGS[2].
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true,
+ Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[]{
+ new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
+ new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
+ }));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[]{
+ new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[2] - 1),
+ new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
+ }));
+ }
+
+ @Test
public void testLockdownAddingAProfile() throws Exception {
final Vpn vpn = createVpn(primaryUser.id);
setMockedUsers(primaryUser);
@@ -310,7 +391,7 @@
final UidRange profile = UidRange.createForUser(tempProfile.id);
// Set lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[3] - 1),
new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
@@ -436,7 +517,7 @@
.cancelAsUser(anyString(), anyInt(), eq(userHandle));
// Start showing a notification for disconnected once always-on.
- vpn.setAlwaysOnPackage(PKGS[0], false);
+ vpn.setAlwaysOnPackage(PKGS[0], false, null);
order.verify(mNotificationManager)
.notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
@@ -450,7 +531,7 @@
.notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
// Notification should be cleared after unsetting always-on package.
- vpn.setAlwaysOnPackage(null, false);
+ vpn.setAlwaysOnPackage(null, false, null);
order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
}
@@ -583,7 +664,9 @@
doAnswer(invocation -> {
final String appName = (String) invocation.getArguments()[0];
final int userId = (int) invocation.getArguments()[1];
- return UserHandle.getUid(userId, packages.get(appName));
+ Integer appId = packages.get(appName);
+ if (appId == null) throw new PackageManager.NameNotFoundException(appName);
+ return UserHandle.getUid(userId, appId);
}).when(mPackageManager).getPackageUidAsUser(anyString(), anyInt());
} catch (Exception e) {
}
diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
index a6b0102..163b00a 100644
--- a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
+++ b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
@@ -190,21 +190,22 @@
@Test
public void testSelectClassAndSamePackage() {
- final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, PACKAGE_A,
- CLASS_C5, CLASS_C6, PACKAGE_C).build();
- acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C);
+ 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_A1L, METHOD_A2M, CLASS_A1,
- CLASS_B3, METHOD_B3P, METHOD_B3Q, METHOD_B4R).build();
+ 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,
+ 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);
}
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/processors/view_inspector/Android.bp b/tools/processors/view_inspector/Android.bp
index 9b5df56..06ff05e 100644
--- a/tools/processors/view_inspector/Android.bp
+++ b/tools/processors/view_inspector/Android.bp
@@ -1,6 +1,8 @@
-java_library_host {
+java_plugin {
name: "view-inspector-annotation-processor",
+ processor_class: "android.processor.view.inspector.PlatformInspectableProcessor",
+
srcs: ["src/java/**/*.java"],
java_resource_dirs: ["src/resources"],
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..d549799 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);
@@ -209,5 +214,6 @@
in IDppCallback callback);
void stopDppSession();
-}
+ void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec);
+}
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/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index d2d711f..96493de 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -787,6 +787,18 @@
public boolean trusted;
/**
+ * This Wifi configuration is created from a {@link WifiNetworkSuggestion}
+ * @hide
+ */
+ public boolean fromWifiNetworkSuggestion;
+
+ /**
+ * This Wifi configuration is created from a {@link WifiNetworkSpecifier}
+ * @hide
+ */
+ public boolean fromWifiNetworkSpecifier;
+
+ /**
* Indicates if the creator of this configuration has expressed that it
* should be considered metered.
*
@@ -1668,6 +1680,8 @@
ephemeral = false;
osu = false;
trusted = true; // Networks are considered trusted by default.
+ fromWifiNetworkSuggestion = false;
+ fromWifiNetworkSpecifier = false;
meteredHint = false;
meteredOverride = METERED_OVERRIDE_NONE;
useExternalScores = false;
@@ -1779,10 +1793,13 @@
if (this.ephemeral) sbuf.append(" ephemeral");
if (this.osu) sbuf.append(" osu");
if (this.trusted) sbuf.append(" trusted");
+ if (this.fromWifiNetworkSuggestion) sbuf.append(" fromWifiNetworkSuggestion");
+ if (this.fromWifiNetworkSpecifier) sbuf.append(" fromWifiNetworkSpecifier");
if (this.meteredHint) sbuf.append(" meteredHint");
if (this.useExternalScores) sbuf.append(" useExternalScores");
if (this.didSelfAdd || this.selfAdded || this.validatedInternetAccess
- || this.ephemeral || this.trusted || this.meteredHint || this.useExternalScores) {
+ || this.ephemeral || this.trusted || this.fromWifiNetworkSuggestion
+ || this.fromWifiNetworkSpecifier || this.meteredHint || this.useExternalScores) {
sbuf.append("\n");
}
if (this.meteredOverride != METERED_OVERRIDE_NONE) {
@@ -2270,6 +2287,8 @@
ephemeral = source.ephemeral;
osu = source.osu;
trusted = source.trusted;
+ fromWifiNetworkSuggestion = source.fromWifiNetworkSuggestion;
+ fromWifiNetworkSpecifier = source.fromWifiNetworkSpecifier;
meteredHint = source.meteredHint;
meteredOverride = source.meteredOverride;
useExternalScores = source.useExternalScores;
@@ -2347,6 +2366,8 @@
dest.writeInt(isLegacyPasspointConfig ? 1 : 0);
dest.writeInt(ephemeral ? 1 : 0);
dest.writeInt(trusted ? 1 : 0);
+ dest.writeInt(fromWifiNetworkSuggestion ? 1 : 0);
+ dest.writeInt(fromWifiNetworkSpecifier ? 1 : 0);
dest.writeInt(meteredHint ? 1 : 0);
dest.writeInt(meteredOverride);
dest.writeInt(useExternalScores ? 1 : 0);
@@ -2418,6 +2439,8 @@
config.isLegacyPasspointConfig = in.readInt() != 0;
config.ephemeral = in.readInt() != 0;
config.trusted = in.readInt() != 0;
+ config.fromWifiNetworkSuggestion = in.readInt() != 0;
+ config.fromWifiNetworkSpecifier = in.readInt() != 0;
config.meteredHint = in.readInt() != 0;
config.meteredOverride = in.readInt();
config.useExternalScores = in.readInt() != 0;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 1fd45e7..40077e1 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -4777,4 +4777,114 @@
});
}
}
-}
+
+ /**
+ * 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();
+ }
+ }
+
+ /**
+ * Provide a Wi-Fi usability score information to be recorded (but not acted upon) by the
+ * framework. The Wi-Fi usability score is derived from {@link WifiUsabilityStatsListener}
+ * where a score is matched to Wi-Fi usability statistics using the sequence number. The score
+ * is used to quantify whether Wi-Fi is usable in a future time.
+ *
+ * @param seqNum Sequence number of the Wi-Fi usability score.
+ * @param score The Wi-Fi usability score.
+ * @param predictionHorizonSec Prediction horizon of the Wi-Fi usability score.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE)
+ public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) {
+ try {
+ mService.updateWifiUsabilityScore(seqNum, score, predictionHorizonSec);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
\ No newline at end of file
diff --git a/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
new file mode 100644
index 0000000..839af54
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+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/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index b3ac9f1..c236c7a 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,20 @@
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();
+ }
+
+ @Override
+ public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 7bff68a..449423f 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -60,6 +60,8 @@
config.setPasspointManagementObjectTree(cookie);
config.trusted = false;
config.updateIdentifier = "1234";
+ config.fromWifiNetworkSpecifier = true;
+ config.fromWifiNetworkSuggestion = true;
MacAddress macBeforeParcel = config.getOrCreateRandomizedMacAddress();
Parcel parcelW = Parcel.obtain();
config.writeToParcel(parcelW, 0);
@@ -76,6 +78,8 @@
assertEquals(macBeforeParcel, reconfig.getOrCreateRandomizedMacAddress());
assertEquals(config.updateIdentifier, reconfig.updateIdentifier);
assertFalse(reconfig.trusted);
+ assertTrue(config.fromWifiNetworkSpecifier);
+ assertTrue(config.fromWifiNetworkSuggestion);
Parcel parcelWW = Parcel.obtain();
reconfig.writeToParcel(parcelWW, 0);
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/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);
+ }
+}