diff options
114 files changed, 5551 insertions, 1223 deletions
diff --git a/api/current.txt b/api/current.txt index 9f39f3af1fec..7dac82ca9487 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3941,7 +3941,8 @@ package android.app { method public boolean isBackgroundRestricted(); method @Deprecated public boolean isInLockTaskMode(); method public boolean isLowRamDevice(); - method public static boolean isRunningInTestHarness(); + method @Deprecated public static boolean isRunningInTestHarness(); + method public static boolean isRunningInUserTestHarness(); method public static boolean isUserAMonkey(); method @RequiresPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) public void killBackgroundProcesses(String); method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int); @@ -11652,6 +11653,7 @@ package android.content.pm { field public static final String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape"; field public static final String FEATURE_SCREEN_PORTRAIT = "android.hardware.screen.portrait"; field public static final String FEATURE_SECURELY_REMOVES_USERS = "android.software.securely_removes_users"; + field public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen"; field public static final String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer"; field public static final String FEATURE_SENSOR_AMBIENT_TEMPERATURE = "android.hardware.sensor.ambient_temperature"; field public static final String FEATURE_SENSOR_BAROMETER = "android.hardware.sensor.barometer"; @@ -25912,6 +25914,23 @@ package android.media { method @Nullable public android.media.Session2Command.Result onSessionCommand(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo, @NonNull android.media.Session2Command, @Nullable android.os.Bundle); } + public abstract class MediaSession2Service extends android.app.Service { + ctor public MediaSession2Service(); + method public final void addSession(@NonNull android.media.MediaSession2); + method @NonNull public final java.util.List<android.media.MediaSession2> getSessions(); + method @CallSuper @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); + method @NonNull public abstract android.media.MediaSession2 onGetPrimarySession(); + method @Nullable public abstract android.media.MediaSession2Service.MediaNotification onUpdateNotification(@NonNull android.media.MediaSession2); + method public final void removeSession(@NonNull android.media.MediaSession2); + field public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service"; + } + + public static class MediaSession2Service.MediaNotification { + ctor public MediaSession2Service.MediaNotification(int, @NonNull android.app.Notification); + method @NonNull public android.app.Notification getNotification(); + method public int getNotificationId(); + } + public final class MediaSync { ctor public MediaSync(); method @NonNull public android.view.Surface createInputSurface(); @@ -28561,6 +28580,28 @@ package android.net { field public int serverAddress; } + public final class DnsResolver { + method public static android.net.DnsResolver getInstance(); + method public void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException; + method public void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException; + method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.InetAddressAnswerListener) throws android.system.ErrnoException; + field public static final int CLASS_IN = 1; // 0x1 + field public static final int FLAG_EMPTY = 0; // 0x0 + field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4 + field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2 + field public static final int FLAG_NO_RETRY = 1; // 0x1 + field public static final int TYPE_A = 1; // 0x1 + field public static final int TYPE_AAAA = 28; // 0x1c + } + + public static interface DnsResolver.InetAddressAnswerListener { + method public void onAnswer(@NonNull java.util.List<java.net.InetAddress>); + } + + public static interface DnsResolver.RawAnswerListener { + method public void onAnswer(@Nullable byte[]); + } + public class InetAddresses { method public static boolean isNumericAddress(String); method public static java.net.InetAddress parseNumericAddress(String); @@ -43596,6 +43637,7 @@ package android.telecom { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts(); method public android.telecom.PhoneAccountHandle getSimCallManager(); method public String getSystemDialerPackage(); + method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber(android.telecom.PhoneAccountHandle); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String, android.telecom.PhoneAccountHandle); @@ -49674,6 +49716,7 @@ package android.view { } public class Surface implements android.os.Parcelable { + ctor public Surface(android.view.SurfaceControl); ctor public Surface(android.graphics.SurfaceTexture); method public int describeContents(); method public boolean isValid(); @@ -49696,6 +49739,38 @@ package android.view { ctor public Surface.OutOfResourcesException(String); } + public final class SurfaceControl implements android.os.Parcelable { + method public int describeContents(); + method public boolean isValid(); + method public void readFromParcel(android.os.Parcel); + method public void release(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.SurfaceControl> CREATOR; + } + + public static class SurfaceControl.Builder { + ctor public SurfaceControl.Builder(); + method public android.view.SurfaceControl build(); + method public android.view.SurfaceControl.Builder setBufferSize(@IntRange(from=0) int, @IntRange(from=0) int); + method @NonNull public android.view.SurfaceControl.Builder setFormat(int); + method public android.view.SurfaceControl.Builder setName(String); + method @NonNull public android.view.SurfaceControl.Builder setOpaque(boolean); + method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl); + } + + public static class SurfaceControl.Transaction implements java.io.Closeable { + ctor public SurfaceControl.Transaction(); + method public void apply(); + method public void close(); + method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction); + method @NonNull public android.view.SurfaceControl.Transaction reparent(@NonNull android.view.SurfaceControl, @Nullable android.view.SurfaceControl); + method @NonNull public android.view.SurfaceControl.Transaction setAlpha(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0, to=1.0) float); + method @NonNull public android.view.SurfaceControl.Transaction setBufferSize(@NonNull android.view.SurfaceControl, @IntRange(from=0) int, @IntRange(from=0) int); + method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int); + method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int); + method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean); + } + public interface SurfaceHolder { method public void addCallback(android.view.SurfaceHolder.Callback); method public android.view.Surface getSurface(); @@ -49740,6 +49815,7 @@ package android.view { ctor public SurfaceView(android.content.Context, android.util.AttributeSet, int, int); method public boolean gatherTransparentRegion(android.graphics.Region); method public android.view.SurfaceHolder getHolder(); + method public android.view.SurfaceControl getSurfaceControl(); method public void setSecure(boolean); method public void setZOrderMediaOverlay(boolean); method public void setZOrderOnTop(boolean); diff --git a/api/system-current.txt b/api/system-current.txt index 2088c14d4653..cdd2d804844b 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1136,19 +1136,46 @@ package android.bluetooth { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean enableNoAutoConnect(); method public boolean isBleScanAlwaysAvailable(); method public boolean isLeEnabled(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean registerMetadataListener(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothAdapter.MetadataListener, android.os.Handler); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean unregisterMetadataListener(android.bluetooth.BluetoothDevice); field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED"; field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE"; } + public abstract class BluetoothAdapter.MetadataListener { + ctor public BluetoothAdapter.MetadataListener(); + method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String); + } + public final class BluetoothDevice implements android.os.Parcelable { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public String getMetadata(int); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, String); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int); field public static final int ACCESS_ALLOWED = 1; // 0x1 field public static final int ACCESS_REJECTED = 2; // 0x2 field public static final int ACCESS_UNKNOWN = 0; // 0x0 + field public static final int METADATA_COMPANION_APP = 4; // 0x4 + field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10 + field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3 + field public static final int METADATA_IS_UNTHETHERED_HEADSET = 6; // 0x6 + field public static final int METADATA_MAIN_ICON = 5; // 0x5 + field public static final int METADATA_MANUFACTURER_NAME = 0; // 0x0 + field public static final int METADATA_MAX_LENGTH = 2048; // 0x800 + field public static final int METADATA_MODEL_NAME = 1; // 0x1 + field public static final int METADATA_SOFTWARE_VERSION = 2; // 0x2 + field public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12; // 0xc + field public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15; // 0xf + field public static final int METADATA_UNTHETHERED_CASE_ICON = 9; // 0x9 + field public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10; // 0xa + field public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13; // 0xd + field public static final int METADATA_UNTHETHERED_LEFT_ICON = 7; // 0x7 + field public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11; // 0xb + field public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14; // 0xe + field public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8; // 0x8 } public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile { @@ -6164,12 +6191,15 @@ package android.service.euicc { method public abstract int onSwitchToSubscription(int, @Nullable String, boolean); method public abstract int onUpdateSubscriptionNickname(int, String, String); field public static final String ACTION_BIND_CARRIER_PROVISIONING_SERVICE = "android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE"; + field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED"; field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS"; field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION"; + field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED"; field @Deprecated public static final String ACTION_RESOLVE_CONFIRMATION_CODE = "android.service.euicc.action.RESOLVE_CONFIRMATION_CODE"; field public static final String ACTION_RESOLVE_DEACTIVATE_SIM = "android.service.euicc.action.RESOLVE_DEACTIVATE_SIM"; field public static final String ACTION_RESOLVE_NO_PRIVILEGES = "android.service.euicc.action.RESOLVE_NO_PRIVILEGES"; field public static final String ACTION_RESOLVE_RESOLVABLE_ERRORS = "android.service.euicc.action.RESOLVE_RESOLVABLE_ERRORS"; + field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED"; field public static final String CATEGORY_EUICC_UI = "android.service.euicc.category.EUICC_UI"; field public static final String EUICC_SERVICE_INTERFACE = "android.service.euicc.EuiccService"; field public static final String EXTRA_RESOLUTION_ALLOW_POLICY_RULES = "android.service.euicc.extra.RESOLUTION_ALLOW_POLICY_RULES"; @@ -6747,6 +6777,7 @@ package android.telecom { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall(); method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging(); method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.WRITE_SECURE_SETTINGS}) public boolean setDefaultDialer(String); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(android.telecom.PhoneAccountHandle); field public static final String EXTRA_CALL_BACK_INTENT = "android.telecom.extra.CALL_BACK_INTENT"; field public static final String EXTRA_CLEAR_MISSED_CALLS_INTENT = "android.telecom.extra.CLEAR_MISSED_CALLS_INTENT"; field public static final String EXTRA_CONNECTION_SERVICE = "android.telecom.extra.CONNECTION_SERVICE"; @@ -7912,9 +7943,12 @@ package android.telephony.euicc { method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata(android.telephony.euicc.DownloadableSubscription, android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus(); + field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED"; field @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public static final String ACTION_OTA_STATUS_CHANGED = "android.telephony.euicc.action.OTA_STATUS_CHANGED"; field public static final String ACTION_PROFILE_SELECTION = "android.telephony.euicc.action.PROFILE_SELECTION"; field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION"; + field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED"; + field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED"; field public static final int EUICC_ACTIVATION_TYPE_BACKUP = 2; // 0x2 field public static final int EUICC_ACTIVATION_TYPE_DEFAULT = 1; // 0x1 field public static final int EUICC_ACTIVATION_TYPE_TRANSFER = 3; // 0x3 @@ -7925,7 +7959,10 @@ package android.telephony.euicc { field public static final int EUICC_OTA_SUCCEEDED = 3; // 0x3 field public static final String EXTRA_ACTIVATION_TYPE = "android.telephony.euicc.extra.ACTIVATION_TYPE"; field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS"; + field public static final String EXTRA_ENABLE_SUBSCRIPTION = "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION"; field public static final String EXTRA_FORCE_PROVISION = "android.telephony.euicc.extra.FORCE_PROVISION"; + field public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.euicc.extra.SUBSCRIPTION_ID"; + field public static final String EXTRA_SUBSCRIPTION_NICKNAME = "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME"; } @IntDef(prefix={"EUICC_OTA_"}, value={android.telephony.euicc.EuiccManager.EUICC_OTA_IN_PROGRESS, android.telephony.euicc.EuiccManager.EUICC_OTA_FAILED, android.telephony.euicc.EuiccManager.EUICC_OTA_SUCCEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_NOT_NEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_STATUS_UNAVAILABLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccManager.OtaStatus { diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java index 4174ad7cd586..1b7fbfe0e32c 100644 --- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java +++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java @@ -23,8 +23,10 @@ import android.os.IUserManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.telecom.Log; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; +import android.text.TextUtils; import com.android.internal.os.BaseCommand; import com.android.internal.telecom.ITelecomService; @@ -45,6 +47,8 @@ public final class Telecom extends BaseCommand { private static final String COMMAND_SET_PHONE_ACCOUNT_ENABLED = "set-phone-account-enabled"; private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled"; private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account"; + private static final String COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT = + "set-user-selected-outgoing-phone-account"; private static final String COMMAND_REGISTER_SIM_PHONE_ACCOUNT = "register-sim-phone-account"; private static final String COMMAND_SET_TEST_CALL_REDIRECTION_APP = "set-test-call-redirection-app"; private static final String COMMAND_SET_TEST_CALL_SCREENING_APP = "set-test-call-screening-app"; @@ -70,6 +74,8 @@ public final class Telecom extends BaseCommand { + "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n" + "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n" + "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n" + + "usage: telecom set-user-selected-outgoing-phone-account <COMPONENT> <ID> " + + "<USER_SN>\n" + "usage: telecom set-test-call-redirection-app <PACKAGE>\n" + "usage: telecom set-test-call-screening-app <PACKAGE>\n" + "usage: telecom set-test-auto-mode-app <PACKAGE>\n" @@ -104,16 +110,18 @@ public final class Telecom extends BaseCommand { mTelecomService = ITelecomService.Stub.asInterface( ServiceManager.getService(Context.TELECOM_SERVICE)); if (mTelecomService == null) { + Log.w(this, "onRun: Can't access telecom manager."); showError("Error: Could not access the Telecom Manager. Is the system running?"); return; } mUserManager = IUserManager.Stub .asInterface(ServiceManager.getService(Context.USER_SERVICE)); if (mUserManager == null) { + Log.w(this, "onRun: Can't access user manager."); showError("Error: Could not access the User Manager. Is the system running?"); return; } - + Log.i(this, "onRun: parsing command."); String command = nextArgRequired(); switch (command) { case COMMAND_SET_PHONE_ACCOUNT_ENABLED: @@ -143,6 +151,9 @@ public final class Telecom extends BaseCommand { case COMMAND_REGISTER_SIM_PHONE_ACCOUNT: runRegisterSimPhoneAccount(); break; + case COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT: + runSetUserSelectedOutgoingPhoneAccount(); + break; case COMMAND_UNREGISTER_PHONE_ACCOUNT: runUnregisterPhoneAccount(); break; @@ -159,6 +170,7 @@ public final class Telecom extends BaseCommand { runWaitOnHandler(); break; default: + Log.w(this, "onRun: unknown command: %s", command); throw new IllegalArgumentException ("unknown command '" + command + "'"); } } @@ -227,6 +239,13 @@ public final class Telecom extends BaseCommand { mTelecomService.setTestPhoneAcctSuggestionComponent(componentName); } + private void runSetUserSelectedOutgoingPhoneAccount() throws RemoteException { + Log.i(this, "runSetUserSelectedOutgoingPhoneAccount"); + final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs(); + mTelecomService.setUserSelectedOutgoingPhoneAccount(handle); + System.out.println("Success - " + handle + " set as default outgoing account."); + } + private void runUnregisterPhoneAccount() throws RemoteException { final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs(); mTelecomService.unregisterPhoneAccount(handle); @@ -256,7 +275,10 @@ public final class Telecom extends BaseCommand { } - private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException{ + private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException { + if (TextUtils.isEmpty(mArgs.peekNextArg())) { + return null; + } final ComponentName component = parseComponentName(nextArgRequired()); final String accountId = nextArgRequired(); final String userSnInStr = nextArgRequired(); @@ -265,6 +287,7 @@ public final class Telecom extends BaseCommand { final int userSn = Integer.parseInt(userSnInStr); userHandle = UserHandle.of(mUserManager.getUserHandle(userSn)); } catch (NumberFormatException ex) { + Log.w(this, "getPhoneAccountHandleFromArgs - invalid user %s", userSnInStr); throw new IllegalArgumentException ("Invalid user serial number " + userSnInStr); } return new PhoneAccountHandle(component, accountId, userHandle); @@ -277,4 +300,4 @@ public final class Telecom extends BaseCommand { } return cn; } -}
\ No newline at end of file +} diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index e0b8d78ebabc..1045b7aa0890 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -3529,12 +3529,32 @@ public class ActivityManager { /** * Returns "true" if device is running in a test harness. + * + * @deprecated this method is false for all user builds. Users looking to check if their device + * is running in a device farm should see {@link #isRunningInUserTestHarness()}. */ + @Deprecated public static boolean isRunningInTestHarness() { return SystemProperties.getBoolean("ro.test_harness", false); } /** + * Returns "true" if the device is running in Test Harness Mode. + * + * <p>Test Harness Mode is a feature that allows devices to run without human interaction in a + * device farm/testing harness (such as Firebase Test Lab). You should check this method if you + * want your app to behave differently when running in a test harness to skip setup screens that + * would impede UI testing. e.g. a keyboard application that has a full screen setup page for + * the first time it is launched. + * + * <p>Note that you should <em>not</em> use this to determine whether or not your app is running + * an instrumentation test, as it is not set for a standard device running a test. + */ + public static boolean isRunningInUserTestHarness() { + return SystemProperties.getBoolean("persist.sys.test_harness", false); + } + + /** * Unsupported compiled sdk warning should always be shown for the intput activity * even in cases where the system would normally not show the warning. E.g. when running in a * test harness. diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index ab8f234766d6..4d3711ae7ff5 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -328,7 +328,7 @@ public class ActivityView extends ViewGroup { } } else { mTmpTransaction.reparent(mRootSurfaceControl, - mSurfaceView.getSurfaceControl().getHandle()).apply(); + mSurfaceView.getSurfaceControl()).apply(); } if (mVirtualDisplay != null) { @@ -390,7 +390,7 @@ public class ActivityView extends ViewGroup { .build(); try { - wm.reparentDisplayContent(displayId, mRootSurfaceControl.getHandle()); + wm.reparentDisplayContent(displayId, mRootSurfaceControl); wm.dontOverrideDisplayInfo(displayId); if (mSingleTaskInstance) { mActivityTaskManager.setDisplayToSingleTaskInstance(displayId); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 1ae0f52ac74e..75c90542ebce 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -19,6 +19,7 @@ package android.app; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -124,6 +125,7 @@ public class KeyguardManager { * * @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; Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL); @@ -176,6 +178,7 @@ public class KeyguardManager { * @throws IllegalStateException if the device has already been provisioned * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @SystemApi public Intent createConfirmFactoryResetCredentialIntent( CharSequence title, CharSequence description, CharSequence alternateButtonLabel) { @@ -231,6 +234,7 @@ public class KeyguardManager { * secure notifications cannot be shown if {@code false} * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) @SystemApi public void setPrivateNotificationsAllowed(boolean allow) { @@ -249,6 +253,7 @@ public class KeyguardManager { * By default, private notifications are allowed. * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) @SystemApi public boolean getPrivateNotificationsAllowed() { diff --git a/core/java/android/app/VrManager.java b/core/java/android/app/VrManager.java index 5b87de48210f..5f1a94c835c3 100644 --- a/core/java/android/app/VrManager.java +++ b/core/java/android/app/VrManager.java @@ -215,19 +215,12 @@ public class VrManager { } /** - * Start VR Input method for the packageName in {@link ComponentName}. - * This method notifies InputMethodManagerService to use VR IME instead of - * regular phone IME. - * @param componentName ComponentName of a VR InputMethod that should be set as selected - * input by InputMethodManagerService. + * This method is not implemented. + * + * @param componentName not used */ @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public void setVrInputMethod(ComponentName componentName) { - try { - mService.setVrInputMethod(componentName); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } } /** diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 86db99bf0b29..7cc953cc254f 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2513,6 +2513,9 @@ public class DevicePolicyManager { * requested quality constant (between the policy set here, the user's preference, and any other * considerations) is the one that is in effect. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2548,6 +2551,9 @@ public class DevicePolicyManager { * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. * + * <p>Note: on devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, + * the password is always treated as empty. + * * @param admin The name of the admin component to check, or {@code null} to aggregate * all admins. */ @@ -2580,6 +2586,9 @@ public class DevicePolicyManager { * {@link #PASSWORD_QUALITY_ALPHANUMERIC}, or {@link #PASSWORD_QUALITY_COMPLEX} with * {@link #setPasswordQuality}. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2609,11 +2618,13 @@ public class DevicePolicyManager { * restrictions on this user and its participating profiles. Restrictions on profiles that have * a separate challenge are not taken into account. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. * - * user and its profiles or a particular one. * @param admin The name of the admin component to check, or {@code null} to aggregate * all admins. */ @@ -2644,6 +2655,9 @@ public class DevicePolicyManager { * setting this value. This constraint is only imposed if the administrator has also requested * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2678,6 +2692,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -2714,6 +2731,9 @@ public class DevicePolicyManager { * setting this value. This constraint is only imposed if the administrator has also requested * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2748,6 +2768,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -2784,6 +2807,9 @@ public class DevicePolicyManager { * only imposed if the administrator has also requested {@link #PASSWORD_QUALITY_COMPLEX} with * {@link #setPasswordQuality}. The default value is 1. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2818,6 +2844,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -2853,6 +2882,9 @@ public class DevicePolicyManager { * setting this value. This constraint is only imposed if the administrator has also requested * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 1. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2887,6 +2919,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -2922,6 +2957,9 @@ public class DevicePolicyManager { * only imposed if the administrator has also requested {@link #PASSWORD_QUALITY_COMPLEX} with * {@link #setPasswordQuality}. The default value is 1. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2955,6 +2993,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -2990,6 +3031,9 @@ public class DevicePolicyManager { * setting this value. This constraint is only imposed if the administrator has also requested * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -3024,6 +3068,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -3060,6 +3107,9 @@ public class DevicePolicyManager { * , {@link #PASSWORD_QUALITY_NUMERIC_COMPLEX} {@link #PASSWORD_QUALITY_ALPHABETIC}, or * {@link #PASSWORD_QUALITY_ALPHANUMERIC} with {@link #setPasswordQuality}. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -3112,6 +3162,7 @@ public class DevicePolicyManager { * @throws SecurityException if {@code admin} is not an active administrator or {@code admin} * does not use {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD} */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void setPasswordExpirationTimeout(@NonNull ComponentName admin, long timeout) { if (mService != null) { try { @@ -3136,6 +3187,7 @@ public class DevicePolicyManager { * @param admin The name of the admin component to check, or {@code null} to aggregate all admins. * @return The timeout for the given admin or the minimum of all timeouts */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public long getPasswordExpirationTimeout(@Nullable ComponentName admin) { if (mService != null) { try { @@ -3160,6 +3212,7 @@ public class DevicePolicyManager { * @param admin The name of the admin component to check, or {@code null} to aggregate all admins. * @return The password expiration time, in milliseconds since epoch. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public long getPasswordExpiration(@Nullable ComponentName admin) { if (mService != null) { try { @@ -3184,12 +3237,14 @@ public class DevicePolicyManager { * all admins. * @return The length of the password history */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getPasswordHistoryLength(@Nullable ComponentName admin) { return getPasswordHistoryLength(admin, myUserId()); } /** @hide per-user version */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getPasswordHistoryLength(@Nullable ComponentName admin, int userHandle) { if (mService != null) { try { @@ -3204,10 +3259,16 @@ public class DevicePolicyManager { /** * Return the maximum password length that the device supports for a * particular password quality. + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always empty. * @param quality The quality being interrogated. * @return Returns the maximum length that the user can enter. */ public int getPasswordMaximumLength(int quality) { + PackageManager pm = mContext.getPackageManager(); + if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ID_ATTESTATION)) { + return 0; + } // Kind-of arbitrary. return 16; } @@ -3218,6 +3279,10 @@ public class DevicePolicyManager { * user and its participating profiles. Restrictions on profiles that have a separate challenge * are not taken into account. The user must be unlocked in order to perform the check. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty - i.e. this method will always return false on such + * devices, provided any password requirements were set. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -3250,6 +3315,9 @@ public class DevicePolicyManager { * explicitly querying the parent profile screen lock complexity via {@link * #getParentProfileInstance}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * @throws IllegalStateException if the user is not unlocked. * @throws SecurityException if the calling application does not have the permission * {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY} @@ -3329,6 +3397,7 @@ public class DevicePolicyManager { * @throws SecurityException if the calling application does not own an active administrator * that uses {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getCurrentFailedPasswordAttempts() { return getCurrentFailedPasswordAttempts(myUserId()); } @@ -3396,6 +3465,7 @@ public class DevicePolicyManager { * both {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void setMaximumFailedPasswordsForWipe(@NonNull ComponentName admin, int num) { if (mService != null) { try { @@ -3419,12 +3489,14 @@ public class DevicePolicyManager { * @param admin The name of the admin component to check, or {@code null} to aggregate * all admins. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getMaximumFailedPasswordsForWipe(@Nullable ComponentName admin) { return getMaximumFailedPasswordsForWipe(admin, myUserId()); } /** @hide per-user version */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getMaximumFailedPasswordsForWipe(@Nullable ComponentName admin, int userHandle) { if (mService != null) { try { @@ -3444,6 +3516,7 @@ public class DevicePolicyManager { * user passed in. * @hide Used only by Keyguard */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle) { if (mService != null) { try { @@ -3514,6 +3587,7 @@ public class DevicePolicyManager { * that uses {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} * @throws IllegalStateException if the calling user is locked or has a managed profile. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public boolean resetPassword(String password, int flags) { throwIfParentInstance("resetPassword"); if (mService != null) { @@ -3557,6 +3631,7 @@ public class DevicePolicyManager { * @throws SecurityException if admin is not a device or profile owner. * @throws IllegalArgumentException if the supplied token is invalid. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public boolean setResetPasswordToken(ComponentName admin, byte[] token) { throwIfParentInstance("setResetPasswordToken"); if (mService != null) { @@ -3576,6 +3651,7 @@ public class DevicePolicyManager { * @return true if the operation is successful, false otherwise. * @throws SecurityException if admin is not a device or profile owner. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public boolean clearResetPasswordToken(ComponentName admin) { throwIfParentInstance("clearResetPasswordToken"); if (mService != null) { @@ -3596,6 +3672,7 @@ public class DevicePolicyManager { * @throws SecurityException if admin is not a device or profile owner. * @throws IllegalStateException if no token has been set. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public boolean isResetPasswordTokenActive(ComponentName admin) { throwIfParentInstance("isResetPasswordTokenActive"); if (mService != null) { @@ -3637,6 +3714,7 @@ public class DevicePolicyManager { * @throws SecurityException if admin is not a device or profile owner. * @throws IllegalStateException if the provided token is not valid. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public boolean resetPasswordWithToken(@NonNull ComponentName admin, String password, byte[] token, int flags) { throwIfParentInstance("resetPassword"); @@ -3742,6 +3820,7 @@ public class DevicePolicyManager { * * @throws SecurityException if {@code admin} is not a device or profile owner. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void setRequiredStrongAuthTimeout(@NonNull ComponentName admin, long timeoutMs) { if (mService != null) { @@ -3766,12 +3845,14 @@ public class DevicePolicyManager { * across all participating admins. * @return The timeout in milliseconds or 0 if not configured for the provided admin. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin) { return getRequiredStrongAuthTimeout(admin, myUserId()); } /** @hide per-user version */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin, @UserIdInt int userId) { if (mService != null) { try { @@ -5350,6 +5431,7 @@ public class DevicePolicyManager { * @hide */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void setActivePasswordState(PasswordMetrics metrics, int userHandle) { if (mService != null) { try { @@ -5363,6 +5445,7 @@ public class DevicePolicyManager { /** * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void reportPasswordChanged(@UserIdInt int userId) { if (mService != null) { try { @@ -5377,6 +5460,7 @@ public class DevicePolicyManager { * @hide */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void reportFailedPasswordAttempt(int userHandle) { if (mService != null) { try { @@ -5391,6 +5475,7 @@ public class DevicePolicyManager { * @hide */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void reportSuccessfulPasswordAttempt(int userHandle) { if (mService != null) { try { @@ -5404,6 +5489,7 @@ public class DevicePolicyManager { /** * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void reportFailedBiometricAttempt(int userHandle) { if (mService != null) { try { @@ -5417,6 +5503,7 @@ public class DevicePolicyManager { /** * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void reportSuccessfulBiometricAttempt(int userHandle) { if (mService != null) { try { @@ -6383,6 +6470,7 @@ public class DevicePolicyManager { * @throws SecurityException if {@code admin} is not an active administrator or does not use * {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES} */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void setTrustAgentConfiguration(@NonNull ComponentName admin, @NonNull ComponentName target, PersistableBundle configuration) { if (mService != null) { @@ -6412,6 +6500,7 @@ public class DevicePolicyManager { * @param agent Which component to get enabled features for. * @return configuration for the given trust agent. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public @Nullable List<PersistableBundle> getTrustAgentConfiguration( @Nullable ComponentName admin, @NonNull ComponentName agent) { return getTrustAgentConfiguration(admin, agent, myUserId()); @@ -6419,6 +6508,7 @@ public class DevicePolicyManager { /** @hide per-user version */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public @Nullable List<PersistableBundle> getTrustAgentConfiguration( @Nullable ComponentName admin, @NonNull ComponentName agent, int userHandle) { if (mService != null) { diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 1945b2fd51de..97bc0796e9ce 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -36,6 +36,7 @@ import android.bluetooth.le.ScanSettings; import android.content.Context; import android.os.BatteryStats; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; @@ -648,6 +649,32 @@ public final class BluetoothAdapter { private final Object mLock = new Object(); private final Map<LeScanCallback, ScanCallback> mLeScanClients; + private static final Map<BluetoothDevice, List<Pair<MetadataListener, Handler>>> + sMetadataListeners = new HashMap<>(); + + /** + * Bluetooth metadata listener. Overrides the default BluetoothMetadataListener + * implementation. + */ + private static final IBluetoothMetadataListener sBluetoothMetadataListener = + new IBluetoothMetadataListener.Stub() { + @Override + public void onMetadataChanged(BluetoothDevice device, int key, String value) { + synchronized (sMetadataListeners) { + if (sMetadataListeners.containsKey(device)) { + List<Pair<MetadataListener, Handler>> list = sMetadataListeners.get(device); + for (Pair<MetadataListener, Handler> pair : list) { + MetadataListener listener = pair.first; + Handler handler = pair.second; + handler.post(() -> { + listener.onMetadataChanged(device, key, value); + }); + } + } + } + return; + } + }; /** * Get a handle to the default local Bluetooth adapter. @@ -2607,6 +2634,16 @@ public final class BluetoothAdapter { } } } + synchronized (sMetadataListeners) { + sMetadataListeners.forEach((device, pair) -> { + try { + mService.registerMetadataListener(sBluetoothMetadataListener, + device); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register metadata listener", e); + } + }); + } } public void onBluetoothServiceDown() { @@ -3090,4 +3127,142 @@ public final class BluetoothAdapter { + "listenUsingInsecureL2capChannel"); return listenUsingInsecureL2capChannel(); } + + /** + * Register a {@link #MetadataListener} to receive update about metadata + * changes for this {@link BluetoothDevice}. + * Registration must be done when Bluetooth is ON and will last until + * {@link #unregisterMetadataListener(BluetoothDevice)} is called, even when Bluetooth + * restarted in the middle. + * All input parameters should not be null or {@link NullPointerException} will be triggered. + * The same {@link BluetoothDevice} and {@link #MetadataListener} pair can only be registered + * once, double registration would cause {@link IllegalArgumentException}. + * + * @param device {@link BluetoothDevice} that will be registered + * @param listener {@link #MetadataListener} that will receive asynchronous callbacks + * @param handler the handler for listener callback + * @return true on success, false on error + * @throws NullPointerException If one of {@code listener}, {@code device} or {@code handler} + * is null. + * @throws IllegalArgumentException The same {@link #MetadataListener} and + * {@link BluetoothDevice} are registered twice. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean registerMetadataListener(BluetoothDevice device, MetadataListener listener, + Handler handler) { + if (DBG) Log.d(TAG, "registerMetdataListener()"); + + final IBluetooth service = mService; + if (service == null) { + Log.e(TAG, "Bluetooth is not enabled. Cannot register metadata listener"); + return false; + } + if (listener == null) { + throw new NullPointerException("listener is null"); + } + if (device == null) { + throw new NullPointerException("device is null"); + } + if (handler == null) { + throw new NullPointerException("handler is null"); + } + + synchronized (sMetadataListeners) { + List<Pair<MetadataListener, Handler>> listenerList = sMetadataListeners.get(device); + if (listenerList == null) { + // Create new listener/handler list for registeration + listenerList = new ArrayList<>(); + sMetadataListeners.put(device, listenerList); + } else { + // Check whether this device was already registed by the lisenter + if (listenerList.stream().anyMatch((pair) -> (pair.first.equals(listener)))) { + throw new IllegalArgumentException("listener was already regestered" + + " for the device"); + } + } + + Pair<MetadataListener, Handler> listenerPair = new Pair(listener, handler); + listenerList.add(listenerPair); + + boolean ret = false; + try { + ret = service.registerMetadataListener(sBluetoothMetadataListener, device); + } catch (RemoteException e) { + Log.e(TAG, "registerMetadataListener fail", e); + } finally { + if (!ret) { + // Remove listener registered earlier when fail. + listenerList.remove(listenerPair); + if (listenerList.isEmpty()) { + // Remove the device if its listener list is empty + sMetadataListeners.remove(device); + } + } + } + return ret; + } + } + + /** + * Unregister all {@link MetadataListener} from this {@link BluetoothDevice}. + * Unregistration can be done when Bluetooth is either ON or OFF. + * {@link #registerMetadataListener(MetadataListener, BluetoothDevice, Handler)} must + * be called before unregisteration. + * Unregistering a device that is not regestered would cause {@link IllegalArgumentException}. + * + * @param device {@link BluetoothDevice} that will be unregistered. it + * should not be null or {@link NullPointerException} will be triggered. + * @return true on success, false on error + * @throws NullPointerException If {@code device} is null. + * @throws IllegalArgumentException If {@code device} has not been registered before. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean unregisterMetadataListener(BluetoothDevice device) { + if (DBG) Log.d(TAG, "unregisterMetdataListener()"); + if (device == null) { + throw new NullPointerException("device is null"); + } + + synchronized (sMetadataListeners) { + if (sMetadataListeners.containsKey(device)) { + sMetadataListeners.remove(device); + } else { + throw new IllegalArgumentException("device was not registered"); + } + + final IBluetooth service = mService; + if (service == null) { + // Bluetooth is OFF, do nothing to Bluetooth service. + return true; + } + try { + return service.unregisterMetadataListener(device); + } catch (RemoteException e) { + Log.e(TAG, "unregisterMetadataListener fail", e); + return false; + } + } + } + + /** + * This abstract class is used to implement {@link BluetoothAdapter} metadata listener. + * @hide + */ + @SystemApi + public abstract class MetadataListener { + /** + * Callback triggered if the metadata of {@link BluetoothDevice} registered in + * {@link #registerMetadataListener}. + * + * @param device changed {@link BluetoothDevice}. + * @param key changed metadata key, one of BluetoothDevice.METADATA_*. + * @param value the new value of metadata. + */ + public void onMetadataChanged(BluetoothDevice device, int key, String value) { + } + } } diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 235dc5c59c2a..17cf702bbdea 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -341,6 +341,137 @@ public final class BluetoothDevice implements Parcelable { "android.bluetooth.device.action.SDP_RECORD"; /** + * Maximum length of a metadata entry, this is to avoid exploding Bluetooth + * disk usage + * @hide + */ + @SystemApi + public static final int METADATA_MAX_LENGTH = 2048; + + /** + * Manufacturer name of this Bluetooth device + * @hide + */ + @SystemApi + public static final int METADATA_MANUFACTURER_NAME = 0; + + /** + * Model name of this Bluetooth device + * @hide + */ + @SystemApi + public static final int METADATA_MODEL_NAME = 1; + + /** + * Software version of this Bluetooth device + * @hide + */ + @SystemApi + public static final int METADATA_SOFTWARE_VERSION = 2; + + /** + * Hardware version of this Bluetooth device + * @hide + */ + @SystemApi + public static final int METADATA_HARDWARE_VERSION = 3; + + /** + * Package name of the companion app, if any + * @hide + */ + @SystemApi + public static final int METADATA_COMPANION_APP = 4; + + /** + * URI to the main icon shown on the settings UI + * @hide + */ + @SystemApi + public static final int METADATA_MAIN_ICON = 5; + + /** + * Whether this device is an untethered headset with left, right and case + * @hide + */ + @SystemApi + public static final int METADATA_IS_UNTHETHERED_HEADSET = 6; + + /** + * URI to icon of the left headset + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_LEFT_ICON = 7; + + /** + * URI to icon of the right headset + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8; + + /** + * URI to icon of the headset charging case + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_CASE_ICON = 9; + + /** + * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} + * is invalid, of the left headset + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10; + + /** + * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} + * is invalid, of the right headset + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11; + + /** + * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} + * is invalid, of the headset charging case + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12; + + /** + * Whether the left headset is charging + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13; + + /** + * Whether the right headset is charging + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14; + + /** + * Whether the headset charging case is charging + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15; + + /** + * URI to the enhanced settings UI slice, null or empty String means + * the UI does not exist + * @hide + */ + @SystemApi + public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; + + /** * Broadcast Action: This intent is used to broadcast the {@link UUID} * wrapped as a {@link android.os.ParcelUuid} of the remote device after it * has been fetched. This intent is sent only when the UUIDs of the remote @@ -2026,4 +2157,61 @@ public final class BluetoothDevice implements Parcelable { Log.e(TAG, "createL2capCocSocket: PLEASE USE THE OFFICIAL API, createInsecureL2capChannel"); return createInsecureL2capChannel(psm); } + + /** + * Set a keyed metadata of this {@link BluetoothDevice} to a + * {@link String} value. + * Only bonded devices's metadata will be persisted across Bluetooth + * restart. + * Metadata will be removed when the device's bond state is moved to + * {@link #BOND_NONE}. + * + * @param key must be within the list of BluetoothDevice.METADATA_* + * @param value the string data to set for key. Must be less than + * {@link BluetoothAdapter#METADATA_MAX_LENGTH} characters in length + * @return true on success, false on error + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean setMetadata(int key, String value) { + final IBluetooth service = sService; + if (service == null) { + Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata"); + return false; + } + if (value.length() > METADATA_MAX_LENGTH) { + throw new IllegalArgumentException("value length is " + value.length() + + ", should not over " + METADATA_MAX_LENGTH); + } + try { + return service.setMetadata(this, key, value); + } catch (RemoteException e) { + Log.e(TAG, "setMetadata fail", e); + return false; + } + } + + /** + * Get a keyed metadata for this {@link BluetoothDevice} as {@link String} + * + * @param key must be within the list of BluetoothDevice.METADATA_* + * @return Metadata of the key as string, null on error or not found + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public String getMetadata(int key) { + final IBluetooth service = sService; + if (service == null) { + Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata"); + return null; + } + try { + return service.getMetadata(this, key); + } catch (RemoteException e) { + Log.e(TAG, "getMetadata fail", e); + return null; + } + } } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b84567383bfb..4efcd30f0082 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2060,6 +2060,14 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has a secure implementation of keyguard, meaning the + * device supports PIN, pattern and password as defined in Android CDD + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device includes an accelerometer. */ @SdkConstant(SdkConstantType.FEATURE) diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index c7320b0d6ccf..c9a4c8270390 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -794,6 +794,12 @@ public abstract class PackageManagerInternal { "android.content.pm.extra.ENABLE_ROLLBACK_INSTALL_FLAGS"; /** + * Extra field name for the set of installed users for a given rollback package. + */ + public static final String EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS = + "android.content.pm.extra.ENABLE_ROLLBACK_INSTALLED_USERS"; + + /** * Used as the {@code enableRollbackCode} argument for * {@link PackageManagerInternal#setEnableRollbackCode} to indicate that * enabling rollback succeeded. @@ -827,4 +833,10 @@ public abstract class PackageManagerInternal { * Ask the package manager to compile layouts in the given package. */ public abstract boolean compileLayouts(String packageName); + + /* + * Inform the package manager that the pending package install identified by + * {@code token} can be completed. + */ + public abstract void finishPackageInstall(int token, boolean didLaunch); } diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl index 7f557cd8bbe8..420bcb69e0c4 100644 --- a/core/java/android/content/rollback/IRollbackManager.aidl +++ b/core/java/android/content/rollback/IRollbackManager.aidl @@ -33,6 +33,12 @@ interface IRollbackManager { void executeRollback(in RollbackInfo rollback, String callerPackageName, in IntentSender statusReceiver); + // Exposed for use from the system server only. Callback from the package + // manager during the install flow when user data can be restored for a given + // package. + void restoreUserData(String packageName, int userId, int appId, long ceDataInode, + String seInfo, int token); + // Exposed for test purposes only. void reloadPersistedData(); diff --git a/core/java/android/net/DnsPacket.java b/core/java/android/net/DnsPacket.java new file mode 100644 index 000000000000..458fb340b196 --- /dev/null +++ b/core/java/android/net/DnsPacket.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.text.TextUtils; + +import com.android.internal.util.BitUtils; + +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.text.DecimalFormat; +import java.text.FieldPosition; +import java.util.ArrayList; +import java.util.List; +import java.util.StringJoiner; + +/** + * Defines basic data for DNS protocol based on RFC 1035. + * Subclasses create the specific format used in DNS packet. + * + * @hide + */ +public abstract class DnsPacket { + public class DnsHeader { + private static final String TAG = "DnsHeader"; + public final int id; + public final int flags; + public final int rcode; + private final int[] mSectionCount; + + /** + * Create a new DnsHeader from a positioned ByteBuffer. + * + * The ByteBuffer must be in network byte order (which is the default). + * Reads the passed ByteBuffer from its current position and decodes a DNS header. + * When this constructor returns, the reading position of the ByteBuffer has been + * advanced to the end of the DNS header record. + * This is meant to chain with other methods reading a DNS response in sequence. + * + */ + DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException { + id = BitUtils.uint16(buf.getShort()); + flags = BitUtils.uint16(buf.getShort()); + rcode = flags & 0xF; + mSectionCount = new int[NUM_SECTIONS]; + for (int i = 0; i < NUM_SECTIONS; ++i) { + mSectionCount[i] = BitUtils.uint16(buf.getShort()); + } + } + + /** + * Get section count by section type. + */ + public int getSectionCount(int sectionType) { + return mSectionCount[sectionType]; + } + } + + public class DnsSection { + private static final int MAXNAMESIZE = 255; + private static final int MAXLABELSIZE = 63; + private static final int MAXLABELCOUNT = 128; + private static final int NAME_NORMAL = 0; + private static final int NAME_COMPRESSION = 0xC0; + private final DecimalFormat byteFormat = new DecimalFormat(); + private final FieldPosition pos = new FieldPosition(0); + + private static final String TAG = "DnsSection"; + + public final String dName; + public final int nsType; + public final int nsClass; + public final long ttl; + private final byte[] mRR; + + /** + * Create a new DnsSection from a positioned ByteBuffer. + * + * The ByteBuffer must be in network byte order (which is the default). + * Reads the passed ByteBuffer from its current position and decodes a DNS section. + * When this constructor returns, the reading position of the ByteBuffer has been + * advanced to the end of the DNS header record. + * This is meant to chain with other methods reading a DNS response in sequence. + * + */ + DnsSection(int sectionType, @NonNull ByteBuffer buf) + throws BufferUnderflowException, ParseException { + dName = parseName(buf, 0 /* Parse depth */); + if (dName.length() > MAXNAMESIZE) { + throw new ParseException("Parse name fail, name size is too long"); + } + nsType = BitUtils.uint16(buf.getShort()); + nsClass = BitUtils.uint16(buf.getShort()); + + if (sectionType != QDSECTION) { + ttl = BitUtils.uint32(buf.getInt()); + final int length = BitUtils.uint16(buf.getShort()); + mRR = new byte[length]; + buf.get(mRR); + } else { + ttl = 0; + mRR = null; + } + } + + /** + * Get a copy of rr. + */ + @Nullable public byte[] getRR() { + return (mRR == null) ? null : mRR.clone(); + } + + /** + * Convert label from {@code byte[]} to {@code String} + * + * It follows the same converting rule as native layer. + * (See ns_name.c in libc) + * + */ + private String labelToString(@NonNull byte[] label) { + final StringBuffer sb = new StringBuffer(); + for (int i = 0; i < label.length; ++i) { + int b = BitUtils.uint8(label[i]); + // Control characters and non-ASCII characters. + if (b <= 0x20 || b >= 0x7f) { + sb.append('\\'); + byteFormat.format(b, sb, pos); + } else if (b == '"' || b == '.' || b == ';' || b == '\\' + || b == '(' || b == ')' || b == '@' || b == '$') { + sb.append('\\'); + sb.append((char) b); + } else { + sb.append((char) b); + } + } + return sb.toString(); + } + + private String parseName(@NonNull ByteBuffer buf, int depth) throws + BufferUnderflowException, ParseException { + if (depth > MAXLABELCOUNT) throw new ParseException("Parse name fails, too many labels"); + final int len = BitUtils.uint8(buf.get()); + final int mask = len & NAME_COMPRESSION; + if (0 == len) { + return ""; + } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) { + throw new ParseException("Parse name fail, bad label type"); + } else if (mask == NAME_COMPRESSION) { + // Name compression based on RFC 1035 - 4.1.4 Message compression + final int offset = ((len & ~NAME_COMPRESSION) << 8) + BitUtils.uint8(buf.get()); + final int oldPos = buf.position(); + if (offset >= oldPos - 2) { + throw new ParseException("Parse compression name fail, invalid compression"); + } + buf.position(offset); + final String pointed = parseName(buf, depth + 1); + buf.position(oldPos); + return pointed; + } else { + final byte[] label = new byte[len]; + buf.get(label); + final String head = labelToString(label); + if (head.length() > MAXLABELSIZE) { + throw new ParseException("Parse name fail, invalid label length"); + } + final String tail = parseName(buf, depth + 1); + return TextUtils.isEmpty(tail) ? head : head + "." + tail; + } + } + } + + public static final int QDSECTION = 0; + public static final int ANSECTION = 1; + public static final int NSSECTION = 2; + public static final int ARSECTION = 3; + private static final int NUM_SECTIONS = ARSECTION + 1; + + private static final String TAG = DnsPacket.class.getSimpleName(); + + protected final DnsHeader mHeader; + protected final List<DnsSection>[] mSections; + + public static class ParseException extends Exception { + public ParseException(String msg) { + super(msg); + } + + public ParseException(String msg, Throwable cause) { + super(msg, cause); + } + } + + protected DnsPacket(@NonNull byte[] data) throws ParseException { + if (null == data) throw new ParseException("Parse header failed, null input data"); + final ByteBuffer buffer; + try { + buffer = ByteBuffer.wrap(data); + mHeader = new DnsHeader(buffer); + } catch (BufferUnderflowException e) { + throw new ParseException("Parse Header fail, bad input data", e); + } + + mSections = new ArrayList[NUM_SECTIONS]; + + for (int i = 0; i < NUM_SECTIONS; ++i) { + final int count = mHeader.getSectionCount(i); + if (count > 0) { + mSections[i] = new ArrayList(count); + } + for (int j = 0; j < count; ++j) { + try { + mSections[i].add(new DnsSection(i, buffer)); + } catch (BufferUnderflowException e) { + throw new ParseException("Parse section fail", e); + } + } + } + } +} diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java new file mode 100644 index 000000000000..6d54264cd89f --- /dev/null +++ b/core/java/android/net/DnsResolver.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static android.net.NetworkUtils.resNetworkQuery; +import static android.net.NetworkUtils.resNetworkResult; +import static android.net.NetworkUtils.resNetworkSend; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.os.MessageQueue; +import android.system.ErrnoException; +import android.util.Log; + +import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + + +/** + * Dns resolver class for asynchronous dns querying + * + */ +public final class DnsResolver { + private static final String TAG = "DnsResolver"; + private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; + private static final int MAXPACKET = 8 * 1024; + + @IntDef(prefix = { "CLASS_" }, value = { + CLASS_IN + }) + @Retention(RetentionPolicy.SOURCE) + @interface QueryClass {} + public static final int CLASS_IN = 1; + + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_A, + TYPE_AAAA + }) + @Retention(RetentionPolicy.SOURCE) + @interface QueryType {} + public static final int TYPE_A = 1; + public static final int TYPE_AAAA = 28; + + @IntDef(prefix = { "FLAG_" }, value = { + FLAG_EMPTY, + FLAG_NO_RETRY, + FLAG_NO_CACHE_STORE, + FLAG_NO_CACHE_LOOKUP + }) + @Retention(RetentionPolicy.SOURCE) + @interface QueryFlag {} + public static final int FLAG_EMPTY = 0; + public static final int FLAG_NO_RETRY = 1 << 0; + public static final int FLAG_NO_CACHE_STORE = 1 << 1; + public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2; + + private static final int DNS_RAW_RESPONSE = 1; + + private static final int NETID_UNSET = 0; + + private static final DnsResolver sInstance = new DnsResolver(); + + /** + * listener for receiving raw answers + */ + public interface RawAnswerListener { + /** + * {@code byte[]} is {@code null} if query timed out + */ + void onAnswer(@Nullable byte[] answer); + } + + /** + * listener for receiving parsed answers + */ + public interface InetAddressAnswerListener { + /** + * Will be called exactly once with all the answers to the query. + * size of addresses will be zero if no available answer could be parsed. + */ + void onAnswer(@NonNull List<InetAddress> addresses); + } + + /** + * Get instance for DnsResolver + */ + public static DnsResolver getInstance() { + return sInstance; + } + + private DnsResolver() {} + + /** + * Pass in a blob and corresponding setting, + * get a blob back asynchronously with the entire raw answer. + * + * @param network {@link Network} specifying which network for querying. + * {@code null} for query on default network. + * @param query blob message + * @param flags flags as a combination of the FLAGS_* constants + * @param handler {@link Handler} to specify the thread + * upon which the {@link RawAnswerListener} will be invoked. + * @param listener a {@link RawAnswerListener} which will be called to notify the caller + * of the result of dns query. + */ + public void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags, + @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException { + final FileDescriptor queryfd = resNetworkSend((network != null + ? network.netId : NETID_UNSET), query, query.length, flags); + registerFDListener(handler.getLooper().getQueue(), queryfd, + answerbuf -> listener.onAnswer(answerbuf)); + } + + /** + * Pass in a domain name and corresponding setting, + * get a blob back asynchronously with the entire raw answer. + * + * @param network {@link Network} specifying which network for querying. + * {@code null} for query on default network. + * @param domain domain name for querying + * @param nsClass dns class as one of the CLASS_* constants + * @param nsType dns resource record (RR) type as one of the TYPE_* constants + * @param flags flags as a combination of the FLAGS_* constants + * @param handler {@link Handler} to specify the thread + * upon which the {@link RawAnswerListener} will be invoked. + * @param listener a {@link RawAnswerListener} which will be called to notify the caller + * of the result of dns query. + */ + public void query(@Nullable Network network, @NonNull String domain, @QueryClass int nsClass, + @QueryType int nsType, @QueryFlag int flags, + @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException { + final FileDescriptor queryfd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags); + registerFDListener(handler.getLooper().getQueue(), queryfd, + answerbuf -> listener.onAnswer(answerbuf)); + } + + /** + * Pass in a domain name and corresponding setting, + * get back a set of InetAddresses asynchronously. + * + * @param network {@link Network} specifying which network for querying. + * {@code null} for query on default network. + * @param domain domain name for querying + * @param flags flags as a combination of the FLAGS_* constants + * @param handler {@link Handler} to specify the thread + * upon which the {@link InetAddressAnswerListener} will be invoked. + * @param listener an {@link InetAddressAnswerListener} which will be called to + * notify the caller of the result of dns query. + * + */ + public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags, + @NonNull Handler handler, @NonNull InetAddressAnswerListener listener) + throws ErrnoException { + final FileDescriptor v4fd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags); + final FileDescriptor v6fd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags); + + final InetAddressAnswerAccumulator accmulator = + new InetAddressAnswerAccumulator(2, listener); + final Consumer<byte[]> consumer = answerbuf -> + accmulator.accumulate(parseAnswers(answerbuf)); + + registerFDListener(handler.getLooper().getQueue(), v4fd, consumer); + registerFDListener(handler.getLooper().getQueue(), v6fd, consumer); + } + + private void registerFDListener(@NonNull MessageQueue queue, + @NonNull FileDescriptor queryfd, @NonNull Consumer<byte[]> answerConsumer) { + queue.addOnFileDescriptorEventListener( + queryfd, + FD_EVENTS, + (fd, events) -> { + byte[] answerbuf = null; + try { + // TODO: Implement result function in Java side instead of using JNI + // Because JNI method close fd prior than unregistering fd on + // event listener. + answerbuf = resNetworkResult(fd); + } catch (ErrnoException e) { + Log.e(TAG, "resNetworkResult:" + e.toString()); + } + answerConsumer.accept(answerbuf); + + // Unregister this fd listener + return 0; + }); + } + + private class DnsAddressAnswer extends DnsPacket { + private static final String TAG = "DnsResolver.DnsAddressAnswer"; + private static final boolean DBG = false; + + private final int mQueryType; + + DnsAddressAnswer(@NonNull byte[] data) throws ParseException { + super(data); + if ((mHeader.flags & (1 << 15)) == 0) { + throw new ParseException("Not an answer packet"); + } + if (mHeader.rcode != 0) { + throw new ParseException("Response error, rcode:" + mHeader.rcode); + } + if (mHeader.getSectionCount(ANSECTION) == 0) { + throw new ParseException("No available answer"); + } + if (mHeader.getSectionCount(QDSECTION) == 0) { + throw new ParseException("No question found"); + } + // Assume only one question per answer packet. (RFC1035) + mQueryType = mSections[QDSECTION].get(0).nsType; + } + + public @NonNull List<InetAddress> getAddresses() { + final List<InetAddress> results = new ArrayList<InetAddress>(); + for (final DnsSection ansSec : mSections[ANSECTION]) { + // Only support A and AAAA, also ignore answers if query type != answer type. + int nsType = ansSec.nsType; + if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) { + continue; + } + try { + results.add(InetAddress.getByAddress(ansSec.getRR())); + } catch (UnknownHostException e) { + if (DBG) { + Log.w(TAG, "rr to address fail"); + } + } + } + return results; + } + } + + private @Nullable List<InetAddress> parseAnswers(@Nullable byte[] data) { + try { + return (data == null) ? null : new DnsAddressAnswer(data).getAddresses(); + } catch (DnsPacket.ParseException e) { + Log.e(TAG, "Parse answer fail " + e.getMessage()); + return null; + } + } + + private class InetAddressAnswerAccumulator { + private final List<InetAddress> mAllAnswers; + private final InetAddressAnswerListener mAnswerListener; + private final int mTargetAnswerCount; + private int mReceivedAnswerCount = 0; + + InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerListener listener) { + mTargetAnswerCount = size; + mAllAnswers = new ArrayList<>(); + mAnswerListener = listener; + } + + public void accumulate(@Nullable List<InetAddress> answer) { + if (null != answer) { + mAllAnswers.addAll(answer); + } + if (++mReceivedAnswerCount == mTargetAnswerCount) { + mAnswerListener.onAnswer(mAllAnswers); + } + } + } +} diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index 4eab49cd0fdf..c996d01fe8e4 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -39,6 +39,8 @@ import java.util.Collection; import java.util.Locale; import java.util.TreeSet; +import android.system.ErrnoException; + /** * Native methods for managing network interfaces. * @@ -138,6 +140,32 @@ public class NetworkUtils { public native static boolean queryUserAccess(int uid, int netId); /** + * DNS resolver series jni method. + * Issue the query {@code msg} on the network designated by {@code netId}. + * {@code flags} is an additional config to control actual querying behavior. + * @return a file descriptor to watch for read events + */ + public static native FileDescriptor resNetworkSend( + int netId, byte[] msg, int msglen, int flags) throws ErrnoException; + + /** + * DNS resolver series jni method. + * Look up the {@code nsClass} {@code nsType} Resource Record (RR) associated + * with Domain Name {@code dname} on the network designated by {@code netId}. + * {@code flags} is an additional config to control actual querying behavior. + * @return a file descriptor to watch for read events + */ + public static native FileDescriptor resNetworkQuery( + int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException; + + /** + * DNS resolver series jni method. + * Read a result for the query associated with the {@code fd}. + * @return a byte array containing blob answer + */ + public static native byte[] resNetworkResult(FileDescriptor fd) throws ErrnoException; + + /** * Add an entry into the ARP cache. */ public static void addArpEntry(Inet4Address ipv4Addr, MacAddress ethAddr, String ifname, diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index ca3905148ede..51c3c4c25ce0 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -72,6 +72,7 @@ import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import java.util.Collection; import java.util.Comparator; import java.util.Objects; import java.util.concurrent.Executor; @@ -831,6 +832,16 @@ public class FileUtils { return false; } + /** {@hide} */ + public static boolean contains(Collection<File> dirs, File file) { + for (File dir : dirs) { + if (contains(dir, file)) { + return true; + } + } + return false; + } + /** * Test if a file lives under the given directory, either as a direct child * or a distant grandchild. diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl index 827170144dea..fe17c6bb14ca 100644 --- a/core/java/android/os/IDeviceIdleController.aidl +++ b/core/java/android/os/IDeviceIdleController.aidl @@ -45,4 +45,6 @@ interface IDeviceIdleController { void exitIdle(String reason); boolean registerMaintenanceActivityListener(IMaintenanceActivityListener listener); void unregisterMaintenanceActivityListener(IMaintenanceActivityListener listener); + int setPreIdleTimeoutMode(int Mode); + void resetPreIdleTimeoutMode(); } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 0942d97df6c7..4ce760f2c4a6 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -1724,6 +1724,25 @@ public final class PowerManager { = "android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED"; /** + * Constant for PreIdleTimeout normal mode (default mode, not short nor extend timeout) . + * @hide + */ + public static final int PRE_IDLE_TIMEOUT_MODE_NORMAL = 0; + + /** + * Constant for PreIdleTimeout long mode (extend timeout to keep in inactive mode + * longer). + * @hide + */ + public static final int PRE_IDLE_TIMEOUT_MODE_LONG = 1; + + /** + * Constant for PreIdleTimeout short mode (short timeout to go to doze mode quickly) + * @hide + */ + public static final int PRE_IDLE_TIMEOUT_MODE_SHORT = 2; + + /** * A wake lock is a mechanism to indicate that your application needs * to have the device stay on. * <p> diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index df1a7131a7ae..714a06126ef7 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -35,6 +35,7 @@ import com.android.internal.util.Preconditions; import java.io.CharArrayWriter; import java.io.File; +import java.util.Locale; /** * Information about a shared/external storage volume for a specific user. @@ -263,6 +264,11 @@ public final class StorageVolume implements Parcelable { return mFsUuid; } + /** {@hide} */ + public @Nullable String getNormalizedUuid() { + return mFsUuid != null ? mFsUuid.toLowerCase(Locale.US) : null; + } + /** * Parse and return volume UUID as FAT volume ID, or return -1 if unable to * parse or UUID is unknown. diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index 8c3aa1750acf..5d310e1c2db9 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -42,6 +42,7 @@ import com.android.internal.util.Preconditions; import java.io.CharArrayWriter; import java.io.File; import java.util.Comparator; +import java.util.Locale; import java.util.Objects; /** @@ -254,6 +255,10 @@ public class VolumeInfo implements Parcelable { return fsUuid; } + public @Nullable String getNormalizedFsUuid() { + return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null; + } + @UnsupportedAppUsage public int getMountUserId() { return mountUserId; diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 3a4998630544..487198ba4d45 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -70,6 +70,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Set; @@ -1224,7 +1226,7 @@ public final class MediaStore { if (sv.isPrimary()) { return VOLUME_EXTERNAL; } else { - return checkArgumentVolumeName(sv.getUuid()); + return checkArgumentVolumeName(sv.getNormalizedUuid()); } } throw new IllegalStateException("Unknown volume at " + path); @@ -2919,7 +2921,7 @@ public final class MediaStore { if (vi.isPrimary()) { volumeNames.add(VOLUME_EXTERNAL); } else { - volumeNames.add(vi.getFsUuid()); + volumeNames.add(vi.getNormalizedFsUuid()); } } } @@ -2953,8 +2955,7 @@ public final class MediaStore { // When not one of the well-known values above, it must be a hex UUID for (int i = 0; i < volumeName.length(); i++) { final char c = volumeName.charAt(i); - if (('a' <= c && c <= 'f') || ('A' <= c && c <= 'F') - || ('0' <= c && c <= '9') || (c == '-')) { + if (('a' <= c && c <= 'f') || ('0' <= c && c <= '9') || (c == '-')) { continue; } else { throw new IllegalArgumentException("Invalid volume name: " + volumeName); @@ -2963,23 +2964,26 @@ public final class MediaStore { return volumeName; } - /** {@hide} */ + /** + * Return path where the given volume is mounted. Not valid for + * {@link #VOLUME_INTERNAL}. + * + * @hide + */ public static @NonNull File getVolumePath(@NonNull String volumeName) throws FileNotFoundException { if (TextUtils.isEmpty(volumeName)) { throw new IllegalArgumentException(); } - if (VOLUME_INTERNAL.equals(volumeName)) { - return Environment.getDataDirectory(); - } else if (VOLUME_EXTERNAL.equals(volumeName)) { + if (VOLUME_EXTERNAL.equals(volumeName)) { return Environment.getExternalStorageDirectory(); } final StorageManager sm = AppGlobals.getInitialApplication() .getSystemService(StorageManager.class); for (VolumeInfo vi : sm.getVolumes()) { - if (Objects.equals(vi.getFsUuid(), volumeName)) { + if (Objects.equals(vi.getNormalizedFsUuid(), volumeName)) { final File path = vi.getPathForUser(UserHandle.myUserId()); if (path != null) { return path; @@ -2992,6 +2996,33 @@ public final class MediaStore { } /** + * Return paths that should be scanned for the given volume. + * + * @hide + */ + public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName) + throws FileNotFoundException { + if (TextUtils.isEmpty(volumeName)) { + throw new IllegalArgumentException(); + } + + final ArrayList<File> res = new ArrayList<>(); + if (VOLUME_INTERNAL.equals(volumeName)) { + res.add(new File(Environment.getRootDirectory(), "media")); + res.add(new File(Environment.getOemDirectory(), "media")); + res.add(new File(Environment.getProductDirectory(), "media")); + } else { + res.add(getVolumePath(volumeName)); + final UserManager um = AppGlobals.getInitialApplication() + .getSystemService(UserManager.class); + if (VOLUME_EXTERNAL.equals(volumeName) && um.isDemoUser()) { + res.add(Environment.getDataPreloadsMediaDirectory()); + } + } + return res; + } + + /** * Uri for querying the state of the media scanner. */ public static Uri getMediaScannerUri() { diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java index 4dc10cd2e4cc..ffb524d5c2e4 100644 --- a/core/java/android/service/euicc/EuiccService.java +++ b/core/java/android/service/euicc/EuiccService.java @@ -103,10 +103,23 @@ public abstract class EuiccService extends Service { */ public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS"; + /** @see android.telephony.euicc.EuiccManager#ACTION_PROVISION_EMBEDDED_SUBSCRIPTION */ public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION"; + /** @see android.telephony.euicc.EuiccManager#ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED */ + public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = + "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED"; + + /** @see android.telephony.euicc.EuiccManager#ACTION_DELETE_SUBSCRIPTION_PRIVILEGED */ + public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = + "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED"; + + /** @see android.telephony.euicc.EuiccManager#ACTION_RENAME_SUBSCRIPTION_PRIVILEGED */ + public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = + "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED"; + // LUI resolution actions. These are called by the platform to resolve errors in situations that // require user interaction. // TODO(b/33075886): Define extras for any input parameters to these dialogs once they are diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl index f7acfc5918a8..b0269e3325bf 100644 --- a/core/java/android/service/vr/IVrManager.aidl +++ b/core/java/android/service/vr/IVrManager.aidl @@ -110,13 +110,5 @@ interface IVrManager { * @param standy True if the device is entering standby, false if it's exiting standby. */ void setStandbyEnabled(boolean standby); - - /** - * Start VR Input method for the given packageName in {@param componentName}. - * This method notifies InputMethodManagerService to use VR IME instead of - * regular phone IME. - */ - void setVrInputMethod(in ComponentName componentName); - } diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 33b3ff4fef59..7d9ec70a4599 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -237,6 +237,16 @@ public class GestureDetector { private static final int LONG_PRESS = 2; private static final int TAP = 3; + /** + * If a MotionEvent has CLASSIFICATION_AMBIGUOUS_GESTURE set, then certain actions, such as + * scrolling, will be inhibited. However, to account for the possibility of incorrect + * classification, the default scrolling will only be inhibited if the gesture moves beyond + * (default touch slop * AMBIGUOUS_GESTURE_MULTIPLIER). Likewise, the default long press + * timeout will be increased for some situations where the default behaviour + * is to cancel it. + */ + private static final int AMBIGUOUS_GESTURE_MULTIPLIER = 2; + private final Handler mHandler; @UnsupportedAppUsage private final OnGestureListener mListener; @@ -292,27 +302,27 @@ public class GestureDetector { @Override public void handleMessage(Message msg) { switch (msg.what) { - case SHOW_PRESS: - mListener.onShowPress(mCurrentDownEvent); - break; - - case LONG_PRESS: - dispatchLongPress(); - break; - - case TAP: - // If the user's finger is still down, do not count it as a tap - if (mDoubleTapListener != null) { - if (!mStillDown) { - mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); - } else { - mDeferConfirmSingleTap = true; + case SHOW_PRESS: + mListener.onShowPress(mCurrentDownEvent); + break; + + case LONG_PRESS: + dispatchLongPress(); + break; + + case TAP: + // If the user's finger is still down, do not count it as a tap + if (mDoubleTapListener != null) { + if (!mStillDown) { + mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); + } else { + mDeferConfirmSingleTap = true; + } } - } - break; + break; - default: - throw new RuntimeException("Unknown message " + msg); //never + default: + throw new RuntimeException("Unknown message " + msg); //never } } } @@ -427,7 +437,7 @@ public class GestureDetector { if (context == null) { //noinspection deprecation touchSlop = ViewConfiguration.getTouchSlop(); - doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this + doubleTapTouchSlop = touchSlop; // Hack rather than adding a hidden method for this doubleTapSlop = ViewConfiguration.getDoubleTapSlop(); //noinspection deprecation mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity(); @@ -605,6 +615,10 @@ public class GestureDetector { if (mInLongPress || mInContextClick) { break; } + + final int motionClassification = ev.getClassification(); + final boolean hasPendingLongPress = mHandler.hasMessages(LONG_PRESS); + final float scrollX = mLastFocusX - focusX; final float scrollY = mLastFocusY - focusY; if (mIsDoubleTapping) { @@ -615,6 +629,31 @@ public class GestureDetector { final int deltaY = (int) (focusY - mDownFocusY); int distance = (deltaX * deltaX) + (deltaY * deltaY); int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare; + + final boolean ambiguousGesture = + motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE; + final boolean shouldInhibitDefaultAction = + hasPendingLongPress && ambiguousGesture; + if (shouldInhibitDefaultAction) { + // Inhibit default long press + if (distance > slopSquare) { + // The default action here is to remove long press. But if the touch + // slop below gets increased, and we never exceed the modified touch + // slop while still receiving AMBIGUOUS_GESTURE, we risk that *nothing* + // will happen in response to user input. To prevent this, + // reschedule long press with a modified timeout. + mHandler.removeMessages(LONG_PRESS); + final int longPressTimeout = ViewConfiguration.getLongPressTimeout(); + mHandler.sendEmptyMessageAtTime(LONG_PRESS, ev.getDownTime() + + longPressTimeout * AMBIGUOUS_GESTURE_MULTIPLIER); + } + // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll + // until the gesture is resolved. + // However, for safety, simply increase the touch slop in case the + // classification is erroneous. Since the value is squared, multiply twice. + slopSquare *= AMBIGUOUS_GESTURE_MULTIPLIER * AMBIGUOUS_GESTURE_MULTIPLIER; + } + if (distance > slopSquare) { handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); mLastFocusX = focusX; @@ -633,6 +672,12 @@ public class GestureDetector { mLastFocusX = focusX; mLastFocusY = focusY; } + final boolean deepPress = + motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS; + if (deepPress && hasPendingLongPress) { + mHandler.removeMessages(LONG_PRESS); + mHandler.sendEmptyMessage(LONG_PRESS); + } break; case MotionEvent.ACTION_UP: diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 330d72f139db..42ac8801629f 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -51,6 +51,7 @@ import android.view.IInputFilter; import android.view.AppTransitionAnimationSpec; import android.view.WindowContentFrameStats; import android.view.WindowManager; +import android.view.SurfaceControl; /** * System private interface to the window manager. @@ -555,8 +556,8 @@ interface IWindowManager * display content info to any SurfaceControl, as this would be a security issue. * * @param displayId The id of the display. - * @param surfaceControlHandle The SurfaceControl handle that the top level layers for the + * @param surfaceControlHandle The SurfaceControl that the top level layers for the * display should be re-parented to. */ - void reparentDisplayContent(int displayId, in IBinder surfaceControlHandle); + void reparentDisplayContent(int displayId, in SurfaceControl sc); } diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index f3cb3767ec9d..7fcb2afa48a9 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -185,6 +185,18 @@ public class Surface implements Parcelable { } /** + * Create a Surface assosciated with a given {@link SurfaceControl}. Buffers submitted to this + * surface will be displayed by the system compositor according to the parameters + * specified by the control. Multiple surfaces may be constructed from one SurfaceControl, + * but only one can be connected (e.g. have an active EGL context) at a time. + * + * @param from The SurfaceControl to assosciate this Surface with + */ + public Surface(SurfaceControl from) { + copyFrom(from); + } + + /** * Create Surface from a {@link SurfaceTexture}. * * Images drawn to the Surface will be made available to the {@link @@ -494,7 +506,6 @@ public class Surface implements Parcelable { * in to it. * * @param other {@link SurfaceControl} to copy from. - * * @hide */ @UnsupportedAppUsage diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 5e98236f7535..863b717008d2 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -27,6 +27,10 @@ import static android.view.Surface.ROTATION_90; import static android.view.SurfaceControlProto.HASH_CODE; import static android.view.SurfaceControlProto.NAME; +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.Size; import android.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; @@ -58,10 +62,16 @@ import libcore.util.NativeAllocationRegistry; import java.io.Closeable; /** - * SurfaceControl - * @hide + * Handle to an on-screen Surface managed by the system compositor. The SurfaceControl is + * a combination of a buffer source, and metadata about how to display the buffers. + * By constructing a {@link Surface} from this SurfaceControl you can submit buffers to be + * composited. Using {@link SurfaceControl.Transaction} you can manipulate various + * properties of how the buffer will be displayed on-screen. SurfaceControl's are + * arranged into a scene-graph like hierarchy, and as such any SurfaceControl may have + * a parent. Geometric properties like transform, crop, and Z-ordering will be inherited + * from the parent, as if the child were content in the parents buffer stream. */ -public class SurfaceControl implements Parcelable { +public final class SurfaceControl implements Parcelable { private static final String TAG = "SurfaceControl"; private static native long nativeCreate(SurfaceSession session, String name, @@ -103,6 +113,8 @@ public class SurfaceControl implements Parcelable { float dtdy, float dsdy); private static native void nativeSetColorTransform(long transactionObj, long nativeObject, float[] matrix, float[] translation); + private static native void nativeSetGeometry(long transactionObj, long nativeObject, + Rect sourceCrop, Rect dest, long orientation); private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color); private static native void nativeSetFlags(long transactionObj, long nativeObject, int flags, int mask); @@ -156,7 +168,7 @@ public class SurfaceControl implements Parcelable { private static native void nativeReparentChildren(long transactionObj, long nativeObject, IBinder handle); private static native void nativeReparent(long transactionObj, long nativeObject, - IBinder parentHandle); + long newParentNativeObject); private static native void nativeSeverChildren(long transactionObj, long nativeObject); private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject, int scalingMode); @@ -331,8 +343,7 @@ public class SurfaceControl implements Parcelable { */ public static final int BUILT_IN_DISPLAY_ID_HDMI = 1; - /* Display power modes * / - + // Display power modes. /** * Display power mode off: used while blanking the screen. * Use only with {@link SurfaceControl#setDisplayPowerMode}. @@ -403,7 +414,6 @@ public class SurfaceControl implements Parcelable { /** * Builder class for {@link SurfaceControl} objects. - * @hide */ public static class Builder { private SurfaceSession mSession; @@ -427,8 +437,14 @@ public class SurfaceControl implements Parcelable { } /** - * Construct a new {@link SurfaceControl} with the set parameters. - * @hide + * Begin building a SurfaceControl. + */ + public Builder() { + } + + /** + * Construct a new {@link SurfaceControl} with the set parameters. The builder + * remains valid. */ public SurfaceControl build() { if (mWidth < 0 || mHeight < 0) { @@ -447,7 +463,6 @@ public class SurfaceControl implements Parcelable { * Set a debugging-name for the SurfaceControl. * * @param name A name to identify the Surface in debugging. - * @hide */ public Builder setName(String name) { mName = name; @@ -459,9 +474,9 @@ public class SurfaceControl implements Parcelable { * * @param width The buffer width in pixels. * @param height The buffer height in pixels. - * @hide */ - public Builder setBufferSize(int width, int height) { + public Builder setBufferSize(@IntRange(from = 0) int width, + @IntRange(from = 0) int height) { if (width < 0 || height < 0) { throw new IllegalArgumentException( "width and height must be positive"); @@ -474,8 +489,8 @@ public class SurfaceControl implements Parcelable { /** * Set the pixel format of the controlled surface's buffers, using constants from * {@link android.graphics.PixelFormat}. - * @hide */ + @NonNull public Builder setFormat(@PixelFormat.Format int format) { mFormat = format; return this; @@ -490,6 +505,7 @@ public class SurfaceControl implements Parcelable { * @param protectedContent Whether to require a protected sink. * @hide */ + @NonNull public Builder setProtected(boolean protectedContent) { if (protectedContent) { mFlags |= PROTECTED_APP; @@ -506,6 +522,7 @@ public class SurfaceControl implements Parcelable { * not a complete prevention of readback as {@link #setProtected}. * @hide */ + @NonNull public Builder setSecure(boolean secure) { if (secure) { mFlags |= SECURE; @@ -537,8 +554,8 @@ public class SurfaceControl implements Parcelable { * If the underlying buffer lacks an alpha channel, it is as if setOpaque(true) * were set automatically. * @param opaque Whether the Surface is OPAQUE. - * @hide */ + @NonNull public Builder setOpaque(boolean opaque) { if (opaque) { mFlags |= OPAQUE; @@ -556,9 +573,9 @@ public class SurfaceControl implements Parcelable { * of the parent. * * @param parent The parent control. - * @hide */ - public Builder setParent(SurfaceControl parent) { + @NonNull + public Builder setParent(@Nullable SurfaceControl parent) { mParent = parent; return this; } @@ -673,9 +690,6 @@ public class SurfaceControl implements Parcelable { private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags, SurfaceControl parent, int windowType, int ownerUid) throws OutOfResourcesException, IllegalArgumentException { - if (session == null) { - throw new IllegalArgumentException("session must not be null"); - } if (name == null) { throw new IllegalArgumentException("name must not be null"); } @@ -729,9 +743,6 @@ public class SurfaceControl implements Parcelable { mCloseGuard.open("release"); } - /** - * @hide - */ public void readFromParcel(Parcel in) { if (in == null) { throw new IllegalArgumentException("source must not be null"); @@ -748,17 +759,11 @@ public class SurfaceControl implements Parcelable { assignNativeObject(object); } - /** - * @hide - */ @Override public int describeContents() { return 0; } - /** - * @hide - */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mName); @@ -791,9 +796,6 @@ public class SurfaceControl implements Parcelable { proto.end(token); } - /** - * @hide - */ public static final Creator<SurfaceControl> CREATOR = new Creator<SurfaceControl>() { public SurfaceControl createFromParcel(Parcel in) { @@ -823,10 +825,12 @@ public class SurfaceControl implements Parcelable { } /** - * Release the local reference to the server-side surface. - * Always call release() when you're done with a Surface. - * This will make the surface invalid. - * @hide + * Release the local reference to the server-side surface. The surface + * may continue to exist on-screen as long as its parent continues + * to exist. To explicitly remove a surface from the screen use + * {@link Transaction#reparent} with a null-parent. + * + * Always call release() when you're done with a SurfaceControl. */ public void release() { if (mNativeObject != 0) { @@ -866,7 +870,10 @@ public class SurfaceControl implements Parcelable { } /** - * @hide + * Check whether this instance points to a valid layer with the system-compositor. For + * example this may be false if construction failed, or the layer was released. + * + * @return Whether this SurfaceControl is valid. */ public boolean isValid() { return mNativeObject != 0; @@ -962,9 +969,9 @@ public class SurfaceControl implements Parcelable { /** * @hide */ - public void reparent(IBinder newParentHandle) { + public void reparent(SurfaceControl newParent) { synchronized(SurfaceControl.class) { - sGlobalTransaction.reparent(this, newParentHandle); + sGlobalTransaction.reparent(this, newParent); } } @@ -1270,9 +1277,6 @@ public class SurfaceControl implements Parcelable { } } - /** - * @hide - */ @Override public String toString() { return "Surface(name=" + mName + ")/@0x" + @@ -1286,6 +1290,7 @@ public class SurfaceControl implements Parcelable { /** * Describes the properties of a physical display known to surface flinger. + * @hide */ public static final class PhysicalDisplayInfo { /** @@ -1777,9 +1782,12 @@ public class SurfaceControl implements Parcelable { } /** - * @hide + * An atomic set of changes to a set of SurfaceControl. */ public static class Transaction implements Closeable { + /** + * @hide + */ public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( Transaction.class.getClassLoader(), nativeGetNativeTransactionFinalizer(), 512); @@ -1789,9 +1797,12 @@ public class SurfaceControl implements Parcelable { Runnable mFreeNativeResources; /** - * @hide + * Open a new transaction object. The transaction may be filed with commands to + * manipulate {@link SurfaceControl} instances, and then applied atomically with + * {@link #apply}. Eventually the user should invoke {@link #close}, when the object + * is no longer required. Note however that re-using a transaction after a call to apply + * is allowed as a convenience. */ - @UnsupportedAppUsage public Transaction() { mNativeObject = nativeCreateTransaction(); mFreeNativeResources @@ -1801,9 +1812,7 @@ public class SurfaceControl implements Parcelable { /** * Apply the transaction, clearing it's state, and making it usable * as a new transaction. - * @hide */ - @UnsupportedAppUsage public void apply() { apply(false); } @@ -1811,7 +1820,6 @@ public class SurfaceControl implements Parcelable { /** * Close the transaction, if the transaction was not already applied this will cancel the * transaction. - * @hide */ @Override public void close() { @@ -1841,6 +1849,27 @@ public class SurfaceControl implements Parcelable { } /** + * Toggle the visibility of a given Layer and it's sub-tree. + * + * @param sc The SurfaceControl for which to set the visibility + * @param visible The new visibility + * @return This transaction object. + */ + @NonNull + public Transaction setVisibility(@NonNull SurfaceControl sc, boolean visible) { + sc.checkNotReleased(); + if (visible) { + return show(sc); + } else { + return hide(sc); + } + } + + /** + * Request that a given surface and it's sub-tree be shown. + * + * @param sc The surface to show. + * @return This transaction. * @hide */ @UnsupportedAppUsage @@ -1851,6 +1880,10 @@ public class SurfaceControl implements Parcelable { } /** + * Request that a given surface and it's sub-tree be hidden. + * + * @param sc The surface to hidden. + * @return This transaction. * @hide */ @UnsupportedAppUsage @@ -1871,10 +1904,17 @@ public class SurfaceControl implements Parcelable { } /** - * @hide + * Set the default buffer size for the SurfaceControl, if there is an + * {@link Surface} assosciated with the control, then + * this will be the default size for buffers dequeued from it. + * @param sc The surface to set the buffer size for. + * @param w The default width + * @param h The default height + * @return This Transaction */ - @UnsupportedAppUsage - public Transaction setBufferSize(SurfaceControl sc, int w, int h) { + @NonNull + public Transaction setBufferSize(@NonNull SurfaceControl sc, + @IntRange(from = 0) int w, @IntRange(from = 0) int h) { sc.checkNotReleased(); mResizedSurfaces.put(sc, new Point(w, h)); nativeSetSize(mNativeObject, sc.mNativeObject, w, h); @@ -1882,10 +1922,17 @@ public class SurfaceControl implements Parcelable { } /** - * @hide + * Set the Z-order for a given SurfaceControl, relative to it's siblings. + * If two siblings share the same Z order the ordering is undefined. Surfaces + * with a negative Z will be placed below the parent surface. + * + * @param sc The SurfaceControl to set the Z order on + * @param z The Z-order + * @return This Transaction. */ - @UnsupportedAppUsage - public Transaction setLayer(SurfaceControl sc, int z) { + @NonNull + public Transaction setLayer(@NonNull SurfaceControl sc, + @IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int z) { sc.checkNotReleased(); nativeSetLayer(mNativeObject, sc.mNativeObject, z); return this; @@ -1912,10 +1959,15 @@ public class SurfaceControl implements Parcelable { } /** - * @hide + * Set the alpha for a given surface. If the alpha is non-zero the SurfaceControl + * will be blended with the Surfaces under it according to the specified ratio. + * + * @param sc The given SurfaceControl. + * @param alpha The alpha to set. */ - @UnsupportedAppUsage - public Transaction setAlpha(SurfaceControl sc, float alpha) { + @NonNull + public Transaction setAlpha(@NonNull SurfaceControl sc, + @FloatRange(from = 0.0, to = 1.0) float alpha) { sc.checkNotReleased(); nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha); return this; @@ -1947,6 +1999,25 @@ public class SurfaceControl implements Parcelable { } /** + * Specify how the buffer assosciated with this Surface is mapped in to the + * parent coordinate space. The source frame will be scaled to fit the destination + * frame, after being rotated according to the orientation parameter. + * + * @param sc The SurfaceControl to specify the geometry of + * @param sourceCrop The source rectangle in buffer space. Or null for the entire buffer. + * @param destFrame The destination rectangle in parent space. Or null for the source frame. + * @param orientation The buffer rotation + * @return This transaction object. + */ + @NonNull + public Transaction setGeometry(@NonNull SurfaceControl sc, @Nullable Rect sourceCrop, + @Nullable Rect destFrame, @Surface.Rotation int orientation) { + sc.checkNotReleased(); + nativeSetGeometry(mNativeObject, sc.mNativeObject, sourceCrop, destFrame, orientation); + return this; + } + + /** * @hide */ @UnsupportedAppUsage @@ -2023,20 +2094,20 @@ public class SurfaceControl implements Parcelable { return this; } - @UnsupportedAppUsage /** * @hide */ + @UnsupportedAppUsage public Transaction setLayerStack(SurfaceControl sc, int layerStack) { sc.checkNotReleased(); nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack); return this; } - @UnsupportedAppUsage /** * @hide */ + @UnsupportedAppUsage public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle, long frameNumber) { if (frameNumber < 0) { @@ -2047,10 +2118,10 @@ public class SurfaceControl implements Parcelable { return this; } - @UnsupportedAppUsage /** * @hide */ + @UnsupportedAppUsage public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface, long frameNumber) { if (frameNumber < 0) { @@ -2071,13 +2142,25 @@ public class SurfaceControl implements Parcelable { return this; } - /** Re-parents a specific child layer to a new parent - * @hide + /** + * Re-parents a given layer to a new parent. Children inherit transform (position, scaling) + * crop, visibility, and Z-ordering from their parents, as if the children were pixels within the + * parent Surface. + * + * @param sc The SurfaceControl to reparent + * @param newParent The new parent for the given control. + * @return This Transaction */ - public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) { + @NonNull + public Transaction reparent(@NonNull SurfaceControl sc, + @Nullable SurfaceControl newParent) { sc.checkNotReleased(); - nativeReparent(mNativeObject, sc.mNativeObject, - newParentHandle); + long otherObject = 0; + if (newParent != null) { + newParent.checkNotReleased(); + otherObject = newParent.mNativeObject; + } + nativeReparent(mNativeObject, sc.mNativeObject, otherObject); return this; } @@ -2245,9 +2328,12 @@ public class SurfaceControl implements Parcelable { /** * Merge the other transaction into this transaction, clearing the * other transaction as if it had been applied. - * @hide + * + * @param other The transaction to merge in to this one. + * @return This transaction. */ - public Transaction merge(Transaction other) { + @NonNull + public Transaction merge(@NonNull Transaction other) { mResizedSurfaces.putAll(other.mResizedSurfaces); other.mResizedSurfaces.clear(); nativeMergeTransaction(mNativeObject, other.mNativeObject); diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 61fb00d3fe59..45e6c50d9ada 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -120,10 +120,11 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb final Rect mScreenRect = new Rect(); SurfaceSession mSurfaceSession; - SurfaceControlWithBackground mSurfaceControl; + SurfaceControl mSurfaceControl; // In the case of format changes we switch out the surface in-place // we need to preserve the old one until the new one has drawn. SurfaceControl mDeferredDestroySurfaceControl; + SurfaceControl mBackgroundControl; final Rect mTmpRect = new Rect(); final Configuration mConfiguration = new Configuration(); @@ -487,6 +488,29 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb } } + private void updateBackgroundVisibilityInTransaction() { + if (mBackgroundControl == null) { + return; + } + if ((mSurfaceFlags & PixelFormat.OPAQUE) == 0) { + mBackgroundControl.show(); + mBackgroundControl.setLayer(Integer.MIN_VALUE); + } else { + mBackgroundControl.hide(); + } + } + + private void releaseSurfaces() { + if (mSurfaceControl != null) { + mSurfaceControl.destroy(); + mSurfaceControl = null; + } + if (mBackgroundControl != null) { + mBackgroundControl.destroy(); + mBackgroundControl = null; + } + } + /** @hide */ protected void updateSurface() { if (!mHaveFrame) { @@ -553,14 +577,21 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb updateOpaqueFlag(); final String name = "SurfaceView - " + viewRoot.getTitle().toString(); - mSurfaceControl = new SurfaceControlWithBackground( - name, - (mSurfaceFlags & SurfaceControl.OPAQUE) != 0, - new SurfaceControl.Builder(mSurfaceSession) - .setBufferSize(mSurfaceWidth, mSurfaceHeight) - .setFormat(mFormat) - .setParent(viewRoot.getSurfaceControl()) - .setFlags(mSurfaceFlags)); + mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession) + .setName(name) + .setOpaque((mSurfaceFlags & SurfaceControl.OPAQUE) != 0) + .setBufferSize(mSurfaceWidth, mSurfaceHeight) + .setFormat(mFormat) + .setParent(viewRoot.getSurfaceControl()) + .setFlags(mSurfaceFlags) + .build(); + mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession) + .setName("Background for -" + name) + .setOpaque(true) + .setColorLayer(true) + .setParent(mSurfaceControl) + .build(); + } else if (mSurfaceControl == null) { return; } @@ -577,11 +608,13 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb SurfaceControl.openTransaction(); try { mSurfaceControl.setLayer(mSubLayer); + if (mViewVisibility) { mSurfaceControl.show(); } else { mSurfaceControl.hide(); } + updateBackgroundVisibilityInTransaction(); // While creating the surface, we will set it's initial // geometry. Outside of that though, we should generally @@ -724,8 +757,7 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb if (mSurfaceControl != null && !mSurfaceCreated) { mSurface.release(); - mSurfaceControl.destroy(); - mSurfaceControl = null; + releaseSurfaces(); } } } catch (Exception ex) { @@ -823,7 +855,6 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb final ViewRootImpl viewRoot = getViewRootImpl(); applySurfaceTransforms(mSurfaceControl, position, frameNumber); - applySurfaceTransforms(mSurfaceControl.mBackgroundControl, position, frameNumber); applyChildSurfaceTransaction_renderWorker(mRtTransaction, viewRoot.mSurface, frameNumber); @@ -950,7 +981,19 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb * @hide */ public void setResizeBackgroundColor(int bgColor) { - mSurfaceControl.setBackgroundColor(bgColor); + if (mBackgroundControl == null) { + return; + } + + final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f, + Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f }; + + SurfaceControl.openTransaction(); + try { + mBackgroundControl.setColor(colorComponents); + } finally { + SurfaceControl.closeTransaction(); + } } @UnsupportedAppUsage @@ -1128,154 +1171,12 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb }; /** - * @hide + * Return a SurfaceControl which can be used for parenting Surfaces to + * this SurfaceView. + * + * @return The SurfaceControl for this SurfaceView. */ public SurfaceControl getSurfaceControl() { return mSurfaceControl; } - - class SurfaceControlWithBackground extends SurfaceControl { - SurfaceControl mBackgroundControl; - private boolean mOpaque = true; - public boolean mVisible = false; - - public SurfaceControlWithBackground(String name, boolean opaque, SurfaceControl.Builder b) - throws Exception { - super(b.setName(name).build()); - - mBackgroundControl = b.setName("Background for -" + name) - .setFormat(OPAQUE) - // Unset the buffer size of the background color layer. - .setBufferSize(0, 0) - .setColorLayer(true) - .build(); - mOpaque = opaque; - } - - @Override - public void setAlpha(float alpha) { - super.setAlpha(alpha); - mBackgroundControl.setAlpha(alpha); - } - - @Override - public void setLayer(int zorder) { - super.setLayer(zorder); - // -3 is below all other child layers as SurfaceView never goes below -2 - mBackgroundControl.setLayer(-3); - } - - @Override - public void setPosition(float x, float y) { - super.setPosition(x, y); - mBackgroundControl.setPosition(x, y); - } - - @Override - public void setBufferSize(int w, int h) { - super.setBufferSize(w, h); - // The background surface is a color layer so we do not set a size. - } - - @Override - public void setWindowCrop(Rect crop) { - super.setWindowCrop(crop); - mBackgroundControl.setWindowCrop(crop); - } - - @Override - public void setWindowCrop(int width, int height) { - super.setWindowCrop(width, height); - mBackgroundControl.setWindowCrop(width, height); - } - - @Override - public void setLayerStack(int layerStack) { - super.setLayerStack(layerStack); - mBackgroundControl.setLayerStack(layerStack); - } - - @Override - public void setOpaque(boolean isOpaque) { - super.setOpaque(isOpaque); - mOpaque = isOpaque; - updateBackgroundVisibility(); - } - - @Override - public void setSecure(boolean isSecure) { - super.setSecure(isSecure); - } - - @Override - public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) { - super.setMatrix(dsdx, dtdx, dsdy, dtdy); - mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy); - } - - @Override - public void hide() { - super.hide(); - mVisible = false; - updateBackgroundVisibility(); - } - - @Override - public void show() { - super.show(); - mVisible = true; - updateBackgroundVisibility(); - } - - @Override - public void destroy() { - super.destroy(); - mBackgroundControl.destroy(); - } - - @Override - public void release() { - super.release(); - mBackgroundControl.release(); - } - - @Override - public void setTransparentRegionHint(Region region) { - super.setTransparentRegionHint(region); - mBackgroundControl.setTransparentRegionHint(region); - } - - @Override - public void deferTransactionUntil(IBinder handle, long frame) { - super.deferTransactionUntil(handle, frame); - mBackgroundControl.deferTransactionUntil(handle, frame); - } - - @Override - public void deferTransactionUntil(Surface barrier, long frame) { - super.deferTransactionUntil(barrier, frame); - mBackgroundControl.deferTransactionUntil(barrier, frame); - } - - /** Set the color to fill the background with. */ - private void setBackgroundColor(int bgColor) { - final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f, - Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f }; - - SurfaceControl.openTransaction(); - try { - mBackgroundControl.setColor(colorComponents); - } finally { - SurfaceControl.closeTransaction(); - } - } - - void updateBackgroundVisibility() { - if (mOpaque && mVisible) { - mBackgroundControl.show(); - } else { - mBackgroundControl.hide(); - } - } - } } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 86c5f188ffab..10b99eca0fa8 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -922,19 +922,6 @@ public final class InputMethodManager { } } - /** - * Returns a list of VR InputMethod currently installed. - * @hide - */ - @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) - public List<InputMethodInfo> getVrInputMethodList() { - try { - return mService.getVrInputMethodList(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - public List<InputMethodInfo> getEnabledInputMethodList() { try { return mService.getEnabledInputMethodList(); diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java index 605df040da59..f91b837410ba 100644 --- a/core/java/com/android/internal/util/CollectionUtils.java +++ b/core/java/com/android/internal/util/CollectionUtils.java @@ -327,10 +327,6 @@ public class CollectionUtils { } } - public static @NonNull <T> List<T> defeatNullable(@Nullable List<T> val) { - return (val != null) ? val : Collections.emptyList(); - } - /** * @return the first element if not empty/null, null otherwise */ diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 356d178cc4eb..8194a920d331 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -35,7 +35,6 @@ interface IInputMethodManager { // TODO: Use ParceledListSlice instead List<InputMethodInfo> getInputMethodList(); - List<InputMethodInfo> getVrInputMethodList(); // TODO: Use ParceledListSlice instead List<InputMethodInfo> getEnabledInputMethodList(); List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId, diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index d5dc703408e4..8d3c482104f7 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -34,6 +34,7 @@ import android.app.trust.TrustManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.AsyncTask; import android.os.Handler; @@ -176,6 +177,7 @@ public class LockPatternUtils { private UserManager mUserManager; private final Handler mHandler; private final SparseLongArray mLockoutDeadlines = new SparseLongArray(); + private Boolean mHasSecureLockScreen; /** * Use {@link TrustManager#isTrustUsuallyManaged(int)}. @@ -706,6 +708,10 @@ public class LockPatternUtils { * @param userId the user whose pattern is to be saved. */ public void saveLockPattern(List<LockPatternView.Cell> pattern, String savedPattern, int userId) { + if (!hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires the lock screen feature."); + } if (pattern == null || pattern.size() < MIN_LOCK_PATTERN_SIZE) { throw new IllegalArgumentException("pattern must not be null and at least " + MIN_LOCK_PATTERN_SIZE + " dots long."); @@ -801,6 +807,10 @@ public class LockPatternUtils { /** Update the encryption password if it is enabled **/ private void updateEncryptionPassword(final int type, final String password) { + if (!hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires the lock screen feature."); + } if (!isDeviceEncryptionEnabled()) { return; } @@ -835,6 +845,10 @@ public class LockPatternUtils { */ public void saveLockPassword(String password, String savedPassword, int requestedQuality, int userHandle) { + if (!hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires the lock screen feature."); + } if (password == null || password.length() < MIN_LOCK_PASSWORD_SIZE) { throw new IllegalArgumentException("password must not be null and at least " + "of length " + MIN_LOCK_PASSWORD_SIZE); @@ -1621,6 +1635,10 @@ public class LockPatternUtils { */ public boolean setLockCredentialWithToken(String credential, int type, int requestedQuality, long tokenHandle, byte[] token, int userId) { + if (!hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires the lock screen feature."); + } LockSettingsInternal localService = getLockSettingsInternal(); if (type != CREDENTIAL_TYPE_NONE) { if (TextUtils.isEmpty(credential) || credential.length() < MIN_LOCK_PASSWORD_SIZE) { @@ -1854,6 +1872,17 @@ public class LockPatternUtils { return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0; } + /** + * Return true if the device supports the lock screen feature, false otherwise. + */ + public boolean hasSecureLockScreen() { + if (mHasSecureLockScreen == null) { + mHasSecureLockScreen = Boolean.valueOf(mContext.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)); + } + return mHasSecureLockScreen.booleanValue(); + } + public static boolean userOwnsFrpCredential(Context context, UserInfo info) { return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled(context); } diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 9b138ebb760a..7eddcfe425d3 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -16,8 +16,11 @@ #define LOG_TAG "NetUtils" +#include <vector> + #include "jni.h" #include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedLocalRef.h> #include "NetdClient.h" #include <utils/misc.h> #include <android_runtime/AndroidRuntime.h> @@ -55,6 +58,31 @@ static const uint32_t kUDPSrcPortIndirectOffset = kEtherHeaderLen + offsetof(udp static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest); static const uint16_t kDhcpClientPort = 68; +constexpr int MAXPACKETSIZE = 8 * 1024; +// FrameworkListener limits the size of commands to 1024 bytes. TODO: fix this. +constexpr int MAXCMDSIZE = 1024; + +static void throwErrnoException(JNIEnv* env, const char* functionName, int error) { + ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName)); + if (detailMessage.get() == NULL) { + // Not really much we can do here. We're probably dead in the water, + // but let's try to stumble on... + env->ExceptionClear(); + } + static jclass errnoExceptionClass = + MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException")); + + static jmethodID errnoExceptionCtor = + GetMethodIDOrDie(env, errnoExceptionClass, + "<init>", "(Ljava/lang/String;I)V"); + + jobject exception = env->NewObject(errnoExceptionClass, + errnoExceptionCtor, + detailMessage.get(), + error); + env->Throw(reinterpret_cast<jthrowable>(exception)); +} + static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd) { struct sock_filter filter_code[] = { @@ -372,6 +400,63 @@ static void android_net_utils_addArpEntry(JNIEnv *env, jobject thiz, jbyteArray } } +static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId, + jstring dname, jint ns_class, jint ns_type, jint flags) { + const jsize javaCharsCount = env->GetStringLength(dname); + const jsize byteCountUTF8 = env->GetStringUTFLength(dname); + + // Only allow dname which could be simply formatted to UTF8. + // In native layer, res_mkquery would re-format the input char array to packet. + std::vector<char> queryname(byteCountUTF8 + 1, 0); + + env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data()); + int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags); + + if (fd < 0) { + throwErrnoException(env, "resNetworkQuery", -fd); + return nullptr; + } + + return jniCreateFileDescriptor(env, fd); +} + +static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId, + jbyteArray msg, jint msgLen, jint flags) { + uint8_t data[MAXCMDSIZE]; + + checkLenAndCopy(env, msg, msgLen, data); + int fd = resNetworkSend(netId, data, msgLen, flags); + + if (fd < 0) { + throwErrnoException(env, "resNetworkSend", -fd); + return nullptr; + } + + return jniCreateFileDescriptor(env, fd); +} + +static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) { + int fd = jniGetFDFromFileDescriptor(env, javaFd); + int rcode; + std::vector<uint8_t> buf(MAXPACKETSIZE, 0); + + int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE); + if (res < 0) { + throwErrnoException(env, "resNetworkResult", -res); + return nullptr; + } + + jbyteArray answer = env->NewByteArray(res); + if (answer == nullptr) { + throwErrnoException(env, "resNetworkResult", ENOMEM); + return nullptr; + } else { + env->SetByteArrayRegion(answer, 0, res, + reinterpret_cast<jbyte*>(buf.data())); + } + + return answer; +} // ---------------------------------------------------------------------------- @@ -391,6 +476,9 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter }, { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter }, { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket }, + { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend }, + { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery }, + { "resNetworkResult", "(Ljava/io/FileDescriptor;)[B", (void*) android_net_utils_resNetworkResult }, }; int register_android_net_NetworkUtils(JNIEnv* env) diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 897427fd8787..0453195e6a1d 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -126,7 +126,12 @@ static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj, jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject, jint windowType, jint ownerUid) { ScopedUtfChars name(env, nameStr); - sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj)); + sp<SurfaceComposerClient> client; + if (sessionObj != NULL) { + client = android_view_SurfaceSession_getClient(env, sessionObj); + } else { + client = SurfaceComposerClient::getDefault(); + } SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject); sp<SurfaceControl> surface; status_t err = client->createSurfaceChecked( @@ -277,6 +282,21 @@ static void nativeSetPosition(JNIEnv* env, jclass clazz, jlong transactionObj, transaction->setPosition(ctrl, x, y); } +static void nativeSetGeometry(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, + jobject sourceObj, jobject dstObj, jlong orientation) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject); + + Rect source, dst; + if (sourceObj != NULL) { + source = rectFromObj(env, sourceObj); + } + if (dstObj != NULL) { + dst = rectFromObj(env, dstObj); + } + transaction->setGeometry(ctrl, source, dst, orientation); +} + static void nativeSetGeometryAppliesWithResize(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject) { @@ -868,13 +888,13 @@ static void nativeReparentChildren(JNIEnv* env, jclass clazz, jlong transactionO static void nativeReparent(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, - jobject newParentObject) { + jlong newParentObject) { auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject); - sp<IBinder> parentHandle = ibinderForJavaObject(env, newParentObject); + auto newParent = reinterpret_cast<SurfaceControl *>(newParentObject); { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); - transaction->reparent(ctrl, parentHandle); + transaction->reparent(ctrl, newParent != NULL ? newParent->getHandle() : NULL); } } @@ -1063,7 +1083,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeDeferTransactionUntilSurface }, {"nativeReparentChildren", "(JJLandroid/os/IBinder;)V", (void*)nativeReparentChildren } , - {"nativeReparent", "(JJLandroid/os/IBinder;)V", + {"nativeReparent", "(JJJ)V", (void*)nativeReparent }, {"nativeSeverChildren", "(JJ)V", (void*)nativeSeverChildren } , @@ -1087,6 +1107,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { {"nativeGetDisplayedContentSample", "(Landroid/os/IBinder;JJ)Landroid/hardware/display/DisplayedContentSample;", (void*)nativeGetDisplayedContentSample }, + {"nativeSetGeometry", "(JJLandroid/graphics/Rect;Landroid/graphics/Rect;J)V", + (void*)nativeSetGeometry } }; int register_android_view_SurfaceControl(JNIEnv* env) diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 702800827f26..2e7184b4f0fb 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -493,10 +493,7 @@ static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) { // Apply system or app filter based on uid. if (uid >= AID_APP_START) { if (is_child_zygote) { - // set_app_zygote_seccomp_filter(); - // TODO(b/111434506) install the filter; for now, install the app filter - // which is more restrictive. - set_app_seccomp_filter(); + set_app_zygote_seccomp_filter(); } else { set_app_seccomp_filter(); } @@ -1645,14 +1642,10 @@ static void com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter( return; } - // TODO(b/111434506) install the filter - - /* bool installed = install_setuidgid_seccomp_filter(uidGidMin, uidGidMax); if (!installed) { RuntimeAbort(env, __LINE__, "Could not install setuid/setgid seccomp filter."); } - */ } /** diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index 7f3ea7a249ba..e68f9dbbc9b7 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -221,6 +221,8 @@ message ConstantsProto { optional bool use_heartbeats = 23; message TimeController { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // Whether or not TimeController should skip setting wakeup alarms for jobs that aren't // ready now. optional bool skip_not_ready_jobs = 1; @@ -228,6 +230,8 @@ message ConstantsProto { optional TimeController time_controller = 25; message QuotaController { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // How much time each app will have to run jobs within their standby bucket window. optional int64 allowed_time_per_period_ms = 1; // How much time the package should have before transitioning from out-of-quota to in-quota. @@ -251,6 +255,21 @@ message ConstantsProto { optional int64 rare_window_size_ms = 6; // The maximum amount of time an app can have its jobs running within a 24 hour window. optional int64 max_execution_time_ms = 7; + // The maximum number of jobs an app can run within this particular standby bucket's + // window size. + optional int32 max_job_count_active = 8; + // The maximum number of jobs an app can run within this particular standby bucket's + // window size. + optional int32 max_job_count_working = 9; + // The maximum number of jobs an app can run within this particular standby bucket's + // window size. + optional int32 max_job_count_frequent = 10; + // The maximum number of jobs an app can run within this particular standby bucket's + // window size. + optional int32 max_job_count_rare = 11; + // The maximum number of jobs that should be allowed to run in the past + // {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}. + optional int32 max_job_count_per_allowed_time = 12; } optional QuotaController quota_controller = 24; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ea0c8e250fe2..96b8dc222e7e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1748,6 +1748,10 @@ <permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED" android:protectionLevel="signature" /> + <!-- @hide Allows the device to be reset, clearing all data and enables Test Harness Mode. --> + <permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE" + android:protectionLevel="signature" /> + <!-- ================================== --> <!-- Permissions for accessing accounts --> <!-- ================================== --> diff --git a/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java new file mode 100644 index 000000000000..1a81c2c9fd41 --- /dev/null +++ b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.test.filters; + +import android.os.Bundle; + +import com.android.test.filters.SelectTest; + +/** + * JUnit test filter that select Window Manager Service related tests from FrameworksCoreTests. + * + * <p>Use this filter when running FrameworksCoreTests as + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.server.wm.test.filters.CoreTestsFilter \ + * -e selectTest_verbose true \ + * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner + * </pre> + */ +public final class CoreTestsFilter extends SelectTest { + + private static final String[] SELECTED_CORE_TESTS = { + "android.app.servertransaction.", // all tests under the package. + "android.view.DisplayCutoutTest", + "android.view.InsetsControllerTest", + "android.view.InsetsSourceTest", + "android.view.InsetsSourceConsumerTest", + "android.view.InsetsStateTest", + }; + + public CoreTestsFilter(Bundle testArgs) { + super(addSelectTest(testArgs, SELECTED_CORE_TESTS)); + } +} diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java index 03cf3eb6a2b9..9913531cdf13 100644 --- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java @@ -21,6 +21,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -67,6 +68,7 @@ public class LockPatternUtilsTest { // TODO(b/63758238): stop spying the class under test mLockPatternUtils = spy(new LockPatternUtils(context)); when(mLockPatternUtils.getLockSettings()).thenReturn(ils); + doReturn(true).when(mLockPatternUtils).hasSecureLockScreen(); final UserInfo userInfo = Mockito.mock(UserInfo.class); when(userInfo.isDemo()).thenReturn(isDemoUser); diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 2848b89a45ca..af016d5d4be9 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1100,7 +1100,8 @@ public class AudioSystem (1 << STREAM_RING) | (1 << STREAM_NOTIFICATION) | (1 << STREAM_SYSTEM) | - (1 << STREAM_VOICE_CALL); + (1 << STREAM_VOICE_CALL) | + (1 << STREAM_BLUETOOTH_SCO); /** * Event posted by AudioTrack and AudioRecord JNI (JNIDeviceCallback) when routing changes. diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java index dd971959dd25..887b4475a4d1 100644 --- a/media/java/android/media/MediaController2.java +++ b/media/java/android/media/MediaController2.java @@ -45,7 +45,7 @@ import java.util.concurrent.Executor; /** * Allows an app to interact with an active {@link MediaSession2} or a - * MediaSession2Service which would provide {@link MediaSession2}. Media buttons and other + * {@link MediaSession2Service} which would provide {@link MediaSession2}. Media buttons and other * commands can be sent to the session. * <p> * This API is not generally intended for third party application developers. @@ -53,7 +53,6 @@ import java.util.concurrent.Executor; * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a> * for consistent behavior across all devices. */ -// TODO: use @link for MediaSession2Service public class MediaController2 implements AutoCloseable { static final String TAG = "MediaController2"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -71,6 +70,8 @@ public class MediaController2 implements AutoCloseable { private final Object mLock = new Object(); //@GuardedBy("mLock") + private boolean mClosed; + //@GuardedBy("mLock") private int mNextSeqNumber; //@GuardedBy("mLock") private Session2Link mSessionBinder; @@ -141,7 +142,14 @@ public class MediaController2 implements AutoCloseable { @Override public void close() { synchronized (mLock) { + if (mClosed) { + // Already closed. Ignore rest of clean up code. + // Note: unbindService() throws IllegalArgumentException when it's called twice. + return; + } + mClosed = true; if (mServiceConnection != null) { + // Note: This should be called even when the bindService() has returned false. mContext.unbindService(mServiceConnection); } if (mSessionBinder != null) { @@ -167,7 +175,7 @@ public class MediaController2 implements AutoCloseable { * If it is not connected yet, it returns {@code null}. * <p> * This may differ with the {@link Session2Token} from the constructor. For example, if the - * controller is created with the token for MediaSession2Service, this would return + * controller is created with the token for {@link MediaSession2Service}, this would return * token for the {@link MediaSession2} in the service. * * @return Session2Token of the connected session, or {@code null} if not connected @@ -316,7 +324,7 @@ public class MediaController2 implements AutoCloseable { MediaController2.this, command, args); if (resultReceiver != null) { if (result == null) { - throw new RuntimeException("onSessionCommand shouldn't return null"); + resultReceiver.send(Session2Command.RESULT_INFO_SKIPPED, null); } else { resultReceiver.send(result.getResultCode(), result.getResultData()); } @@ -433,8 +441,8 @@ public class MediaController2 implements AutoCloseable { * @param controller the controller for this event * @param command the session command * @param args optional arguments - * @return the result for the session command. A runtime exception will be thrown if null - * is returned. + * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED + * will be sent to the session. */ @Nullable public Session2Command.Result onSessionCommand(@NonNull MediaController2 controller, diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java index 3adac7295fff..fdd07fdd52e3 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/java/android/media/MediaSession2.java @@ -90,6 +90,8 @@ public class MediaSession2 implements AutoCloseable { private boolean mClosed; //@GuardedBy("mLock") private boolean mPlaybackActive; + //@GuardedBy("mLock") + private ForegroundServiceEventCallback mForegroundServiceEventCallback; MediaSession2(@NonNull Context context, @NonNull String id, PendingIntent sessionActivity, @NonNull Executor callbackExecutor, @NonNull SessionCallback callback) { @@ -119,6 +121,7 @@ public class MediaSession2 implements AutoCloseable { public void close() { try { List<ControllerInfo> controllerInfos; + ForegroundServiceEventCallback callback; synchronized (mLock) { if (mClosed) { return; @@ -126,11 +129,15 @@ public class MediaSession2 implements AutoCloseable { mClosed = true; controllerInfos = getConnectedControllers(); mConnectedControllers.clear(); - mCallback.onSessionClosed(this); + callback = mForegroundServiceEventCallback; + mForegroundServiceEventCallback = null; } synchronized (MediaSession2.class) { SESSION_ID_LIST.remove(mSessionId); } + if (callback != null) { + callback.onSessionClosed(this); + } for (ControllerInfo info : controllerInfos) { info.notifyDisconnected(); } @@ -224,11 +231,16 @@ public class MediaSession2 implements AutoCloseable { * @param playbackActive {@code true} if the playback active, {@code false} otherwise. **/ public void setPlaybackActive(boolean playbackActive) { + final ForegroundServiceEventCallback serviceCallback; synchronized (mLock) { if (mPlaybackActive == playbackActive) { return; } mPlaybackActive = playbackActive; + serviceCallback = mForegroundServiceEventCallback; + } + if (serviceCallback != null) { + serviceCallback.onPlaybackActiveChanged(this, playbackActive); } List<ControllerInfo> controllerInfos = getConnectedControllers(); for (ControllerInfo controller : controllerInfos) { @@ -257,6 +269,18 @@ public class MediaSession2 implements AutoCloseable { return mCallback; } + void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) { + synchronized (mLock) { + if (mForegroundServiceEventCallback == callback) { + return; + } + if (mForegroundServiceEventCallback != null && callback != null) { + throw new IllegalStateException("A session cannot be added to multiple services"); + } + mForegroundServiceEventCallback = callback; + } + } + // Called by Session2Link.onConnect and MediaSession2Service.MediaSession2ServiceStub.connect void onConnect(final Controller2Link controller, int callingPid, int callingUid, int seq, Bundle connectionRequest) { @@ -373,7 +397,7 @@ public class MediaSession2 implements AutoCloseable { MediaSession2.this, controllerInfo, command, args); if (resultReceiver != null) { if (result == null) { - throw new RuntimeException("onSessionCommand shouldn't return null"); + resultReceiver.send(Session2Command.RESULT_INFO_SKIPPED, null); } else { resultReceiver.send(result.getResultCode(), result.getResultData()); } @@ -695,8 +719,6 @@ public class MediaSession2 implements AutoCloseable { * This API is not generally intended for third party application developers. */ public abstract static class SessionCallback { - ForegroundServiceEventCallback mForegroundServiceEventCallback; - /** * Called when a controller is created for this session. Return allowed commands for * controller. By default it returns {@code null}. @@ -731,8 +753,8 @@ public class MediaSession2 implements AutoCloseable { * @param controller controller information * @param command the session command * @param args optional arguments - * @return the result for the session command. A runtime exception will be thrown if null - * is returned. + * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED + * will be sent to the session. */ @Nullable public Session2Command.Result onSessionCommand(@NonNull MediaSession2 session, @@ -753,19 +775,10 @@ public class MediaSession2 implements AutoCloseable { public void onCommandResult(@NonNull MediaSession2 session, @NonNull ControllerInfo controller, @NonNull Object token, @NonNull Session2Command command, @NonNull Session2Command.Result result) {} + } - final void onSessionClosed(MediaSession2 session) { - if (mForegroundServiceEventCallback != null) { - mForegroundServiceEventCallback.onSessionClosed(session); - } - } - - void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) { - mForegroundServiceEventCallback = callback; - } - - abstract static class ForegroundServiceEventCallback { - public void onSessionClosed(MediaSession2 session) {} - } + abstract static class ForegroundServiceEventCallback { + public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {} + public void onSessionClosed(MediaSession2 session) {} } } diff --git a/media/java/android/media/MediaSession2Service.java b/media/java/android/media/MediaSession2Service.java index 8fb00fe487e8..5bb746a7f9e3 100644 --- a/media/java/android/media/MediaSession2Service.java +++ b/media/java/android/media/MediaSession2Service.java @@ -19,7 +19,10 @@ package android.media; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.Notification; +import android.app.NotificationManager; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.Bundle; @@ -28,8 +31,6 @@ import android.os.IBinder; import android.util.ArrayMap; import android.util.Log; -import com.android.internal.annotations.GuardedBy; - import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -42,11 +43,7 @@ import java.util.Map; * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a> * for consistent behavior across all devices. - * @hide */ -// TODO: Unhide -// TODO: Add onUpdateNotification(), and calls it to get Notification for startForegroundService() -// when a session's player state becomes playing. public abstract class MediaSession2Service extends Service { /** * The {@link Intent} that must be declared as handled by the service. @@ -56,10 +53,29 @@ public abstract class MediaSession2Service extends Service { private static final String TAG = "MediaSession2Service"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private final MediaSession2.ForegroundServiceEventCallback mForegroundServiceEventCallback = + new MediaSession2.ForegroundServiceEventCallback() { + @Override + public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) { + MediaSession2Service.this.onPlaybackActiveChanged(session, playbackActive); + } + + @Override + public void onSessionClosed(MediaSession2 session) { + removeSession(session); + } + }; + private final Object mLock = new Object(); - @GuardedBy("mLock") + //@GuardedBy("mLock") + private NotificationManager mNotificationManager; + //@GuardedBy("mLock") + private Intent mStartSelfIntent; + //@GuardedBy("mLock") private Map<String, MediaSession2> mSessions = new ArrayMap<>(); - + //@GuardedBy("mLock") + private Map<MediaSession2, MediaNotification> mNotifications = new ArrayMap<>(); + //@GuardedBy("mLock") private MediaSession2ServiceStub mStub; /** @@ -72,7 +88,12 @@ public abstract class MediaSession2Service extends Service { @Override public void onCreate() { super.onCreate(); - mStub = new MediaSession2ServiceStub(this); + synchronized (mLock) { + mStub = new MediaSession2ServiceStub(this); + mStartSelfIntent = new Intent(this, this.getClass()); + mNotificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + } } @CallSuper @@ -80,18 +101,13 @@ public abstract class MediaSession2Service extends Service { @Nullable public IBinder onBind(@NonNull Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction())) { - return mStub; + synchronized (mLock) { + return mStub; + } } return null; } - @CallSuper - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - // TODO: Dispatch media key events to the primary session. - return START_STICKY; - } - /** * Called by the system to notify that it is no longer used and is being removed. Do not call * this method directly. @@ -104,10 +120,12 @@ public abstract class MediaSession2Service extends Service { public void onDestroy() { super.onDestroy(); synchronized (mLock) { - for (MediaSession2 session : mSessions.values()) { - session.getCallback().setForegroundServiceEventCallback(null); + List<MediaSession2> sessions = getSessions(); + for (MediaSession2 session : sessions) { + removeSession(session); } mSessions.clear(); + mNotifications.clear(); } mStub.close(); } @@ -144,6 +162,24 @@ public abstract class MediaSession2Service extends Service { public abstract MediaSession2 onGetPrimarySession(); /** + * Called when notification UI needs update. Override this method to show or cancel your own + * notification UI. + * <p> + * This would be called on {@link MediaSession2}'s callback executor when playback state is + * changed. + * <p> + * With the notification returned here, the service becomes foreground service when the playback + * is started. Apps must request the permission + * {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use this API. It becomes + * background service after the playback is stopped. + * + * @param session a session that needs notification update. + * @return a {@link MediaNotification}. Can be {@code null}. + */ + @Nullable + public abstract MediaNotification onUpdateNotification(@NonNull MediaSession2 session); + + /** * Adds a session to this service. * <p> * Added session will be removed automatically when it's closed, or removed when @@ -161,21 +197,15 @@ public abstract class MediaSession2Service extends Service { } synchronized (mLock) { MediaSession2 previousSession = mSessions.get(session.getSessionId()); - if (previousSession != session) { - if (previousSession != null) { + if (previousSession != null) { + if (previousSession != session) { Log.w(TAG, "Session ID should be unique, ID=" + session.getSessionId() + ", previous=" + previousSession + ", session=" + session); } return; } mSessions.put(session.getSessionId(), session); - session.getCallback().setForegroundServiceEventCallback( - new MediaSession2.SessionCallback.ForegroundServiceEventCallback() { - @Override - public void onSessionClosed(MediaSession2 session) { - removeSession(session); - } - }); + session.setForegroundServiceEventCallback(mForegroundServiceEventCallback); } } @@ -189,8 +219,21 @@ public abstract class MediaSession2Service extends Service { if (session == null) { throw new IllegalArgumentException("session shouldn't be null"); } + MediaNotification notification; synchronized (mLock) { + if (mSessions.get(session.getSessionId()) != session) { + // Session isn't added or removed already. + return; + } mSessions.remove(session.getSessionId()); + notification = mNotifications.remove(session); + } + session.setForegroundServiceEventCallback(null); + if (notification != null) { + mNotificationManager.cancel(notification.getNotificationId()); + } + if (getSessions().isEmpty()) { + stopForeground(false); } } @@ -207,6 +250,78 @@ public abstract class MediaSession2Service extends Service { return list; } + /** + * Called by registered {@link MediaSession2.ForegroundServiceEventCallback} + * + * @param session session with change + * @param playbackActive {@code true} if playback is active. + */ + void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) { + MediaNotification mediaNotification = onUpdateNotification(session); + if (mediaNotification == null) { + // The service implementation doesn't want to use the automatic start/stopForeground + // feature. + return; + } + synchronized (mLock) { + mNotifications.put(session, mediaNotification); + } + int id = mediaNotification.getNotificationId(); + Notification notification = mediaNotification.getNotification(); + if (!playbackActive) { + mNotificationManager.notify(id, notification); + return; + } + // playbackActive == true + startForegroundService(mStartSelfIntent); + startForeground(id, notification); + } + + /** + * Returned by {@link #onUpdateNotification(MediaSession2)} for making session service + * foreground service to keep playback running in the background. It's highly recommended to + * show media style notification here. + */ + public static class MediaNotification { + private final int mNotificationId; + private final Notification mNotification; + + /** + * Default constructor + * + * @param notificationId notification id to be used for + * {@link NotificationManager#notify(int, Notification)}. + * @param notification a notification to make session service run in the foreground. Media + * style notification is recommended here. + */ + public MediaNotification(int notificationId, @NonNull Notification notification) { + if (notification == null) { + throw new IllegalArgumentException("notification shouldn't be null"); + } + mNotificationId = notificationId; + mNotification = notification; + } + + /** + * Gets the id of the notification. + * + * @return the notification id + */ + public int getNotificationId() { + return mNotificationId; + } + + /** + * Gets the notification. + * + * @return the notification + */ + @NonNull + public Notification getNotification() { + return mNotification; + } + } + private static final class MediaSession2ServiceStub extends IMediaSession2Service.Stub implements AutoCloseable { final WeakReference<MediaSession2Service> mService; diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index 3b51c82e06c9..325420b06122 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -21,18 +21,14 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Intent; import android.graphics.Bitmap; +import android.media.session.MediaSession; import android.media.session.MediaSessionLegacyHelper; import android.media.session.PlaybackState; -import android.media.session.MediaSession; import android.os.Bundle; -import android.os.Handler; import android.os.Looper; -import android.os.Message; import android.os.SystemClock; import android.util.Log; -import java.lang.IllegalArgumentException; - /** * RemoteControlClient enables exposing information meant to be consumed by remote controls * capable of displaying metadata, artwork and media transport control buttons. @@ -682,7 +678,7 @@ import java.lang.IllegalArgumentException; // USE_SESSIONS if (mSession != null) { - int pbState = PlaybackState.getStateFromRccState(state); + int pbState = getStateFromRccState(state); long position = hasPosition ? mPlaybackPositionMs : PlaybackState.PLAYBACK_POSITION_UNKNOWN; @@ -718,8 +714,7 @@ import java.lang.IllegalArgumentException; // USE_SESSIONS if (mSession != null) { PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState); - bob.setActions( - PlaybackState.getActionsFromRccControlFlags(transportControlFlags)); + bob.setActions(getActionsFromRccControlFlags(transportControlFlags)); mSessionPlaybackState = bob.build(); mSession.setPlaybackState(mSessionPlaybackState); } @@ -1001,16 +996,19 @@ import java.lang.IllegalArgumentException; * Period for playback position drift checks, 15s when playing at 1x or slower. */ private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000; + /** * Minimum period for playback position drift checks, never more often when every 2s, when * fast forwarding or rewinding. */ private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000; + /** * The value above which the difference between client-reported playback position and * estimated position is considered a drift. */ private final static long POSITION_DRIFT_MAX_MS = 500; + /** * Compute the period at which the estimated playback position should be compared against the * actual playback position. Is a funciton of playback speed. @@ -1025,4 +1023,151 @@ import java.lang.IllegalArgumentException; POSITION_REFRESH_PERIOD_MIN_MS); } } + + /** + * Get the {@link PlaybackState} state for the given + * {@link RemoteControlClient} state. + * + * @param rccState The state used by {@link RemoteControlClient}. + * @return The equivalent state used by {@link PlaybackState}. + */ + private static int getStateFromRccState(int rccState) { + switch (rccState) { + case PLAYSTATE_BUFFERING: + return PlaybackState.STATE_BUFFERING; + case PLAYSTATE_ERROR: + return PlaybackState.STATE_ERROR; + case PLAYSTATE_FAST_FORWARDING: + return PlaybackState.STATE_FAST_FORWARDING; + case PLAYSTATE_NONE: + return PlaybackState.STATE_NONE; + case PLAYSTATE_PAUSED: + return PlaybackState.STATE_PAUSED; + case PLAYSTATE_PLAYING: + return PlaybackState.STATE_PLAYING; + case PLAYSTATE_REWINDING: + return PlaybackState.STATE_REWINDING; + case PLAYSTATE_SKIPPING_BACKWARDS: + return PlaybackState.STATE_SKIPPING_TO_PREVIOUS; + case PLAYSTATE_SKIPPING_FORWARDS: + return PlaybackState.STATE_SKIPPING_TO_NEXT; + case PLAYSTATE_STOPPED: + return PlaybackState.STATE_STOPPED; + default: + return -1; + } + } + + /** + * Get the {@link RemoteControlClient} state for the given + * {@link PlaybackState} state. + * + * @param state The state used by {@link PlaybackState}. + * @return The equivalent state used by {@link RemoteControlClient}. + */ + static int getRccStateFromState(int state) { + switch (state) { + case PlaybackState.STATE_BUFFERING: + return PLAYSTATE_BUFFERING; + case PlaybackState.STATE_ERROR: + return PLAYSTATE_ERROR; + case PlaybackState.STATE_FAST_FORWARDING: + return PLAYSTATE_FAST_FORWARDING; + case PlaybackState.STATE_NONE: + return PLAYSTATE_NONE; + case PlaybackState.STATE_PAUSED: + return PLAYSTATE_PAUSED; + case PlaybackState.STATE_PLAYING: + return PLAYSTATE_PLAYING; + case PlaybackState.STATE_REWINDING: + return PLAYSTATE_REWINDING; + case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: + return PLAYSTATE_SKIPPING_BACKWARDS; + case PlaybackState.STATE_SKIPPING_TO_NEXT: + return PLAYSTATE_SKIPPING_FORWARDS; + case PlaybackState.STATE_STOPPED: + return PLAYSTATE_STOPPED; + default: + return -1; + } + } + + private static long getActionsFromRccControlFlags(int rccFlags) { + long actions = 0; + long flag = 1; + while (flag <= rccFlags) { + if ((flag & rccFlags) != 0) { + actions |= getActionForRccFlag((int) flag); + } + flag = flag << 1; + } + return actions; + } + + static int getRccControlFlagsFromActions(long actions) { + int rccFlags = 0; + long action = 1; + while (action <= actions && action < Integer.MAX_VALUE) { + if ((action & actions) != 0) { + rccFlags |= getRccFlagForAction(action); + } + action = action << 1; + } + return rccFlags; + } + + private static long getActionForRccFlag(int flag) { + switch (flag) { + case FLAG_KEY_MEDIA_PREVIOUS: + return PlaybackState.ACTION_SKIP_TO_PREVIOUS; + case FLAG_KEY_MEDIA_REWIND: + return PlaybackState.ACTION_REWIND; + case FLAG_KEY_MEDIA_PLAY: + return PlaybackState.ACTION_PLAY; + case FLAG_KEY_MEDIA_PLAY_PAUSE: + return PlaybackState.ACTION_PLAY_PAUSE; + case FLAG_KEY_MEDIA_PAUSE: + return PlaybackState.ACTION_PAUSE; + case FLAG_KEY_MEDIA_STOP: + return PlaybackState.ACTION_STOP; + case FLAG_KEY_MEDIA_FAST_FORWARD: + return PlaybackState.ACTION_FAST_FORWARD; + case FLAG_KEY_MEDIA_NEXT: + return PlaybackState.ACTION_SKIP_TO_NEXT; + case FLAG_KEY_MEDIA_POSITION_UPDATE: + return PlaybackState.ACTION_SEEK_TO; + case FLAG_KEY_MEDIA_RATING: + return PlaybackState.ACTION_SET_RATING; + } + return 0; + } + + private static int getRccFlagForAction(long action) { + // We only care about the lower set of actions that can map to rcc + // flags. + int testAction = action < Integer.MAX_VALUE ? (int) action : 0; + switch (testAction) { + case (int) PlaybackState.ACTION_SKIP_TO_PREVIOUS: + return FLAG_KEY_MEDIA_PREVIOUS; + case (int) PlaybackState.ACTION_REWIND: + return FLAG_KEY_MEDIA_REWIND; + case (int) PlaybackState.ACTION_PLAY: + return FLAG_KEY_MEDIA_PLAY; + case (int) PlaybackState.ACTION_PLAY_PAUSE: + return FLAG_KEY_MEDIA_PLAY_PAUSE; + case (int) PlaybackState.ACTION_PAUSE: + return FLAG_KEY_MEDIA_PAUSE; + case (int) PlaybackState.ACTION_STOP: + return FLAG_KEY_MEDIA_STOP; + case (int) PlaybackState.ACTION_FAST_FORWARD: + return FLAG_KEY_MEDIA_FAST_FORWARD; + case (int) PlaybackState.ACTION_SKIP_TO_NEXT: + return FLAG_KEY_MEDIA_NEXT; + case (int) PlaybackState.ACTION_SEEK_TO: + return FLAG_KEY_MEDIA_POSITION_UPDATE; + case (int) PlaybackState.ACTION_SET_RATING: + return FLAG_KEY_MEDIA_RATING; + } + return 0; + } } diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java index 5e9eed737256..f70963a982e4 100644 --- a/media/java/android/media/RemoteController.java +++ b/media/java/android/media/RemoteController.java @@ -632,8 +632,8 @@ import java.util.List; l = this.mOnClientUpdateListener; } if (l != null) { - int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE : PlaybackState - .getRccStateFromState(state.getState()); + int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE + : RemoteControlClient.getRccStateFromState(state.getState()); if (state == null || state.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) { l.onClientPlaybackStateUpdate(playstate); } else { @@ -642,7 +642,7 @@ import java.util.List; } if (state != null) { l.onClientTransportControlUpdate( - PlaybackState.getRccControlFlagsFromActions(state.getActions())); + RemoteControlClient.getRccControlFlagsFromActions(state.getActions())); } } } diff --git a/media/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java index d8f74c518bf4..238cc2b8ee7d 100644 --- a/media/java/android/media/Session2Token.java +++ b/media/java/android/media/Session2Token.java @@ -35,7 +35,7 @@ import java.util.List; import java.util.Objects; /** - * Represents an ongoing {@link MediaSession2} or a MediaSession2Service. + * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}. * If it's representing a session service, it may not be ongoing. * <p> * This API is not generally intended for third party application developers. @@ -48,14 +48,6 @@ import java.util.Objects; * <p> * It can be also obtained by {@link android.media.session.MediaSessionManager}. */ -// New version of MediaSession2.Token for following reasons -// - Stop implementing Parcelable for updatable support -// - Represent session and library service (formerly browser service) in one class. -// Previously MediaSession2.Token was for session and ComponentName was for service. -// This helps controller apps to keep target of dispatching media key events in uniform way. -// For details about the reason, see following. (Android O+) -// android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged -// TODO: use @link for MediaSession2Service public final class Session2Token implements Parcelable { private static final String TAG = "Session2Token"; @@ -85,12 +77,13 @@ public final class Session2Token implements Parcelable { public static final int TYPE_SESSION = 0; /** - * Type for MediaSession2Service. + * Type for {@link MediaSession2Service}. */ public static final int TYPE_SESSION_SERVICE = 1; private final int mUid; - private final @TokenType int mType; + @TokenType + private final int mType; private final String mPackageName; private final String mServiceName; private final Session2Link mSessionLink; @@ -206,14 +199,6 @@ public final class Session2Token implements Parcelable { } /** - * @hide - * @return component name of the session. Can be {@code null} for {@link #TYPE_SESSION}. - */ - public ComponentName getComponentName() { - return mComponentName; - } - - /** * @return type of the token * @see #TYPE_SESSION * @see #TYPE_SESSION_SERVICE @@ -222,10 +207,7 @@ public final class Session2Token implements Parcelable { return mType; } - /** - * @hide - */ - public Session2Link getSessionLink() { + Session2Link getSessionLink() { return mSessionLink; } diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java index 2c57d1f676ed..0d0ec4c78394 100644 --- a/media/java/android/media/session/PlaybackState.java +++ b/media/java/android/media/session/PlaybackState.java @@ -19,7 +19,6 @@ import android.annotation.DrawableRes; import android.annotation.IntDef; import android.annotation.LongDef; import android.annotation.Nullable; -import android.media.RemoteControlClient; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -481,161 +480,6 @@ public final class PlaybackState implements Parcelable { return mExtras; } - /** - * Get the {@link PlaybackState} state for the given - * {@link RemoteControlClient} state. - * - * @param rccState The state used by {@link RemoteControlClient}. - * @return The equivalent state used by {@link PlaybackState}. - * @hide - */ - public static int getStateFromRccState(int rccState) { - switch (rccState) { - case RemoteControlClient.PLAYSTATE_BUFFERING: - return STATE_BUFFERING; - case RemoteControlClient.PLAYSTATE_ERROR: - return STATE_ERROR; - case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: - return STATE_FAST_FORWARDING; - case RemoteControlClient.PLAYSTATE_NONE: - return STATE_NONE; - case RemoteControlClient.PLAYSTATE_PAUSED: - return STATE_PAUSED; - case RemoteControlClient.PLAYSTATE_PLAYING: - return STATE_PLAYING; - case RemoteControlClient.PLAYSTATE_REWINDING: - return STATE_REWINDING; - case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: - return STATE_SKIPPING_TO_PREVIOUS; - case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: - return STATE_SKIPPING_TO_NEXT; - case RemoteControlClient.PLAYSTATE_STOPPED: - return STATE_STOPPED; - default: - return -1; - } - } - - /** - * Get the {@link RemoteControlClient} state for the given - * {@link PlaybackState} state. - * - * @param state The state used by {@link PlaybackState}. - * @return The equivalent state used by {@link RemoteControlClient}. - * @hide - */ - public static int getRccStateFromState(int state) { - switch (state) { - case STATE_BUFFERING: - return RemoteControlClient.PLAYSTATE_BUFFERING; - case STATE_ERROR: - return RemoteControlClient.PLAYSTATE_ERROR; - case STATE_FAST_FORWARDING: - return RemoteControlClient.PLAYSTATE_FAST_FORWARDING; - case STATE_NONE: - return RemoteControlClient.PLAYSTATE_NONE; - case STATE_PAUSED: - return RemoteControlClient.PLAYSTATE_PAUSED; - case STATE_PLAYING: - return RemoteControlClient.PLAYSTATE_PLAYING; - case STATE_REWINDING: - return RemoteControlClient.PLAYSTATE_REWINDING; - case STATE_SKIPPING_TO_PREVIOUS: - return RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS; - case STATE_SKIPPING_TO_NEXT: - return RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS; - case STATE_STOPPED: - return RemoteControlClient.PLAYSTATE_STOPPED; - default: - return -1; - } - } - - /** - * @hide - */ - public static long getActionsFromRccControlFlags(int rccFlags) { - long actions = 0; - long flag = 1; - while (flag <= rccFlags) { - if ((flag & rccFlags) != 0) { - actions |= getActionForRccFlag((int) flag); - } - flag = flag << 1; - } - return actions; - } - - /** - * @hide - */ - public static int getRccControlFlagsFromActions(long actions) { - int rccFlags = 0; - long action = 1; - while (action <= actions && action < Integer.MAX_VALUE) { - if ((action & actions) != 0) { - rccFlags |= getRccFlagForAction(action); - } - action = action << 1; - } - return rccFlags; - } - - private static long getActionForRccFlag(int flag) { - switch (flag) { - case RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS: - return ACTION_SKIP_TO_PREVIOUS; - case RemoteControlClient.FLAG_KEY_MEDIA_REWIND: - return ACTION_REWIND; - case RemoteControlClient.FLAG_KEY_MEDIA_PLAY: - return ACTION_PLAY; - case RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE: - return ACTION_PLAY_PAUSE; - case RemoteControlClient.FLAG_KEY_MEDIA_PAUSE: - return ACTION_PAUSE; - case RemoteControlClient.FLAG_KEY_MEDIA_STOP: - return ACTION_STOP; - case RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD: - return ACTION_FAST_FORWARD; - case RemoteControlClient.FLAG_KEY_MEDIA_NEXT: - return ACTION_SKIP_TO_NEXT; - case RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE: - return ACTION_SEEK_TO; - case RemoteControlClient.FLAG_KEY_MEDIA_RATING: - return ACTION_SET_RATING; - } - return 0; - } - - private static int getRccFlagForAction(long action) { - // We only care about the lower set of actions that can map to rcc - // flags. - int testAction = action < Integer.MAX_VALUE ? (int) action : 0; - switch (testAction) { - case (int) ACTION_SKIP_TO_PREVIOUS: - return RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS; - case (int) ACTION_REWIND: - return RemoteControlClient.FLAG_KEY_MEDIA_REWIND; - case (int) ACTION_PLAY: - return RemoteControlClient.FLAG_KEY_MEDIA_PLAY; - case (int) ACTION_PLAY_PAUSE: - return RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE; - case (int) ACTION_PAUSE: - return RemoteControlClient.FLAG_KEY_MEDIA_PAUSE; - case (int) ACTION_STOP: - return RemoteControlClient.FLAG_KEY_MEDIA_STOP; - case (int) ACTION_FAST_FORWARD: - return RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD; - case (int) ACTION_SKIP_TO_NEXT: - return RemoteControlClient.FLAG_KEY_MEDIA_NEXT; - case (int) ACTION_SEEK_TO: - return RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE; - case (int) ACTION_SET_RATING: - return RemoteControlClient.FLAG_KEY_MEDIA_RATING; - } - return 0; - } - public static final Parcelable.Creator<PlaybackState> CREATOR = new Parcelable.Creator<PlaybackState>() { @Override diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index b903142c44c6..c3c3f25a9f03 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -166,6 +166,8 @@ <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> <uses-permission android:name="android.permission.SUSPEND_APPS" /> <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" /> + <!-- Permission needed to wipe the device for Test Harness Mode --> + <uses-permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE" /> <uses-permission android:name="android.permission.MANAGE_APPOPS" /> diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java index 35abfd4c843e..49db488bc740 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java @@ -676,6 +676,10 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { // 3tap and hold afterLongTapTimeoutTransitionToDraggingState(event); + } else if (isTapOutOfDistanceSlop()) { + + transitionToDelegatingStateAndClear(); + } else if (mDetectTripleTap // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay // to ensure reachability of @@ -921,6 +925,31 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { // TODO: multi-display support for magnification gesture handler mMagnificationController.setForceShowMagnifiableBounds(Display.DEFAULT_DISPLAY, state); } + + /** + * Detects if last action down is out of distance slop between with previous + * one, when triple tap is enabled. + * + * @return true if tap is out of distance slop + */ + boolean isTapOutOfDistanceSlop() { + if (!mDetectTripleTap) return false; + if (mPreLastDown == null || mLastDown == null) { + return false; + } + final boolean outOfDistanceSlop = + GestureUtils.distance(mPreLastDown, mLastDown) > mMultiTapMaxDistance; + if (tapCount() > 0) { + return outOfDistanceSlop; + } + // There's no tap in the queue here. We still need to check if this is the case that + // user tap screen quickly and out of distance slop. + if (outOfDistanceSlop + && !GestureUtils.isTimedOut(mPreLastDown, mLastDown, mMultiTapMaxDelay)) { + return true; + } + return false; + } } private void zoomOn(float centerX, float centerY) { diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index 121a830f05f5..39030aaf3eb4 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -341,6 +341,29 @@ public class DeviceIdleController extends SystemService @VisibleForTesting static final int STATE_QUICK_DOZE_DELAY = 7; + private static final int ACTIVE_REASON_UNKNOWN = 0; + private static final int ACTIVE_REASON_MOTION = 1; + private static final int ACTIVE_REASON_SCREEN = 2; + private static final int ACTIVE_REASON_CHARGING = 3; + private static final int ACTIVE_REASON_UNLOCKED = 4; + private static final int ACTIVE_REASON_FROM_BINDER_CALL = 5; + private static final int ACTIVE_REASON_FORCED = 6; + private static final int ACTIVE_REASON_ALARM = 7; + @VisibleForTesting + static final int SET_IDLE_FACTOR_RESULT_UNINIT = -1; + @VisibleForTesting + static final int SET_IDLE_FACTOR_RESULT_IGNORED = 0; + @VisibleForTesting + static final int SET_IDLE_FACTOR_RESULT_OK = 1; + @VisibleForTesting + static final int SET_IDLE_FACTOR_RESULT_NOT_SUPPORT = 2; + @VisibleForTesting + static final int SET_IDLE_FACTOR_RESULT_INVALID = 3; + @VisibleForTesting + static final long MIN_STATE_STEP_ALARM_CHANGE = 60 * 1000; + @VisibleForTesting + static final float MIN_PRE_IDLE_FACTOR_CHANGE = 0.05f; + @VisibleForTesting static String stateToString(int state) { switch (state) { @@ -405,6 +428,7 @@ public class DeviceIdleController extends SystemService private long mNextSensingTimeoutAlarmTime; private long mCurIdleBudget; private long mMaintenanceStartTime; + private long mIdleStartTime; private int mActiveIdleOpCount; private PowerManager.WakeLock mActiveIdleWakeLock; // held when there are operations in progress @@ -415,6 +439,17 @@ public class DeviceIdleController extends SystemService private boolean mAlarmsActive; private boolean mReportedMaintenanceActivity; + /* Factor to apply to INACTIVE_TIMEOUT and IDLE_AFTER_INACTIVE_TIMEOUT in order to enter + * STATE_IDLE faster or slower. Don't apply this to SENSING_TIMEOUT or LOCATING_TIMEOUT because: + * - Both of them are shorter + * - Device sensor might take time be to become be stabilized + * Also don't apply the factor if the device is in motion because device motion provides a + * stronger signal than a prediction algorithm. + */ + private float mPreIdleFactor; + private float mLastPreIdleFactor; + private int mActiveReason; + public final AtomicFile mConfigFile; private final RemoteCallbackList<IMaintenanceActivityListener> mMaintenanceActivityListeners = @@ -760,6 +795,10 @@ public class DeviceIdleController extends SystemService * exit doze. Default = true */ private static final String KEY_WAIT_FOR_UNLOCK = "wait_for_unlock"; + private static final String KEY_PRE_IDLE_FACTOR_LONG = + "pre_idle_factor_long"; + private static final String KEY_PRE_IDLE_FACTOR_SHORT = + "pre_idle_factor_short"; /** * This is the time, after becoming inactive, that we go in to the first @@ -987,6 +1026,16 @@ public class DeviceIdleController extends SystemService */ public long NOTIFICATION_WHITELIST_DURATION; + /** + * Pre idle time factor use to make idle delay longer + */ + public float PRE_IDLE_FACTOR_LONG; + + /** + * Pre idle time factor use to make idle delay shorter + */ + public float PRE_IDLE_FACTOR_SHORT; + public boolean WAIT_FOR_UNLOCK; private final ContentResolver mResolver; @@ -1082,6 +1131,8 @@ public class DeviceIdleController extends SystemService NOTIFICATION_WHITELIST_DURATION = mParser.getDurationMillis( KEY_NOTIFICATION_WHITELIST_DURATION, 30 * 1000L); WAIT_FOR_UNLOCK = mParser.getBoolean(KEY_WAIT_FOR_UNLOCK, false); + PRE_IDLE_FACTOR_LONG = mParser.getFloat(KEY_PRE_IDLE_FACTOR_LONG, 1.67f); + PRE_IDLE_FACTOR_SHORT = mParser.getFloat(KEY_PRE_IDLE_FACTOR_SHORT, 0.33f); } } @@ -1196,6 +1247,12 @@ public class DeviceIdleController extends SystemService pw.print(" "); pw.print(KEY_WAIT_FOR_UNLOCK); pw.print("="); pw.println(WAIT_FOR_UNLOCK); + + pw.print(" "); pw.print(KEY_PRE_IDLE_FACTOR_LONG); pw.print("="); + pw.println(PRE_IDLE_FACTOR_LONG); + + pw.print(" "); pw.print(KEY_PRE_IDLE_FACTOR_SHORT); pw.print("="); + pw.println(PRE_IDLE_FACTOR_SHORT); } } @@ -1244,6 +1301,8 @@ public class DeviceIdleController extends SystemService private static final int MSG_FINISH_IDLE_OP = 8; private static final int MSG_REPORT_TEMP_APP_WHITELIST_CHANGED = 9; private static final int MSG_SEND_CONSTRAINT_MONITORING = 10; + private static final int MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR = 11; + private static final int MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR = 12; final class MyHandler extends Handler { MyHandler(Looper looper) { @@ -1373,6 +1432,13 @@ public class DeviceIdleController extends SystemService constraint.stopMonitoring(); } } break; + case MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR: { + updatePreIdleFactor(); + } break; + case MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR: { + updatePreIdleFactor(); + maybeDoImmediateMaintenance(); + } break; } } } @@ -1526,6 +1592,28 @@ public class DeviceIdleController extends SystemService DeviceIdleController.this.unregisterMaintenanceActivityListener(listener); } + @Override public int setPreIdleTimeoutMode(int mode) { + getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER, + null); + long ident = Binder.clearCallingIdentity(); + try { + return DeviceIdleController.this.setPreIdleTimeoutMode(mode); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public void resetPreIdleTimeoutMode() { + getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER, + null); + long ident = Binder.clearCallingIdentity(); + try { + DeviceIdleController.this.resetPreIdleTimeoutMode(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { DeviceIdleController.this.dump(fd, pw, args); } @@ -1768,9 +1856,12 @@ public class DeviceIdleController extends SystemService // Start out assuming we are charging. If we aren't, we will at least get // a battery update the next time the level drops. mCharging = true; + mActiveReason = ACTIVE_REASON_UNKNOWN; mState = STATE_ACTIVE; mLightState = LIGHT_STATE_ACTIVE; mInactiveTimeout = mConstants.INACTIVE_TIMEOUT; + mPreIdleFactor = 1.0f; + mLastPreIdleFactor = 1.0f; } mBinderService = new BinderService(); @@ -2394,6 +2485,7 @@ public class DeviceIdleController extends SystemService public void exitIdleInternal(String reason) { synchronized (this) { + mActiveReason = ACTIVE_REASON_FROM_BINDER_CALL; becomeActiveLocked(reason, Binder.getCallingUid()); } } @@ -2463,6 +2555,7 @@ public class DeviceIdleController extends SystemService } else if (screenOn) { mScreenOn = true; if (!mForceIdle && (!mScreenLocked || !mConstants.WAIT_FOR_UNLOCK)) { + mActiveReason = ACTIVE_REASON_SCREEN; becomeActiveLocked("screen", Process.myUid()); } } @@ -2485,6 +2578,7 @@ public class DeviceIdleController extends SystemService } else if (charging) { mCharging = charging; if (!mForceIdle) { + mActiveReason = ACTIVE_REASON_CHARGING; becomeActiveLocked("charging", Process.myUid()); } } @@ -2516,6 +2610,7 @@ public class DeviceIdleController extends SystemService if (mScreenLocked != showing) { mScreenLocked = showing; if (mScreenOn && !mForceIdle && !mScreenLocked) { + mActiveReason = ACTIVE_REASON_UNLOCKED; becomeActiveLocked("unlocked", Process.myUid()); } } @@ -2587,7 +2682,11 @@ public class DeviceIdleController extends SystemService mState = STATE_INACTIVE; if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE"); resetIdleManagementLocked(); - scheduleAlarmLocked(mInactiveTimeout, false); + long delay = mInactiveTimeout; + if (shouldUseIdleTimeoutFactorLocked()) { + delay = (long) (mPreIdleFactor * delay); + } + scheduleAlarmLocked(delay, false); EventLogTags.writeDeviceIdle(mState, "no activity"); } } @@ -2605,6 +2704,7 @@ public class DeviceIdleController extends SystemService mNextIdlePendingDelay = 0; mNextIdleDelay = 0; mNextLightIdleDelay = 0; + mIdleStartTime = 0; cancelAlarmLocked(); cancelSensingTimeoutAlarmLocked(); cancelLocatingLocked(); @@ -2621,6 +2721,7 @@ public class DeviceIdleController extends SystemService if (mForceIdle) { mForceIdle = false; if (mScreenOn || mCharging) { + mActiveReason = ACTIVE_REASON_FORCED; becomeActiveLocked("exit-force", Process.myUid()); } } @@ -2740,6 +2841,7 @@ public class DeviceIdleController extends SystemService if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) { // Whoops, there is an upcoming alarm. We don't actually want to go idle. if (mState != STATE_ACTIVE) { + mActiveReason = ACTIVE_REASON_ALARM; becomeActiveLocked("alarm", Process.myUid()); becomeInactiveIfAppropriateLocked(); } @@ -2763,7 +2865,11 @@ public class DeviceIdleController extends SystemService // We have now been inactive long enough, it is time to start looking // for motion and sleep some more while doing so. startMonitoringMotionLocked(); - scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false); + long delay = mConstants.IDLE_AFTER_INACTIVE_TIMEOUT; + if (shouldUseIdleTimeoutFactorLocked()) { + delay = (long) (mPreIdleFactor * delay); + } + scheduleAlarmLocked(delay, false); moveToStateLocked(STATE_IDLE_PENDING, reason); break; case STATE_IDLE_PENDING: @@ -2834,6 +2940,7 @@ public class DeviceIdleController extends SystemService " ms."); mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR); if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay); + mIdleStartTime = SystemClock.elapsedRealtime(); mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT); if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) { mNextIdleDelay = mConstants.IDLE_TIMEOUT; @@ -2934,6 +3041,127 @@ public class DeviceIdleController extends SystemService } } + @VisibleForTesting + int setPreIdleTimeoutMode(int mode) { + return setPreIdleTimeoutFactor(getPreIdleTimeoutByMode(mode)); + } + + @VisibleForTesting + float getPreIdleTimeoutByMode(int mode) { + switch (mode) { + case PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG: { + return mConstants.PRE_IDLE_FACTOR_LONG; + } + case PowerManager.PRE_IDLE_TIMEOUT_MODE_SHORT: { + return mConstants.PRE_IDLE_FACTOR_SHORT; + } + case PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL: { + return 1.0f; + } + default: { + Slog.w(TAG, "Invalid time out factor mode: " + mode); + return 1.0f; + } + } + } + + @VisibleForTesting + float getPreIdleTimeoutFactor() { + return mPreIdleFactor; + } + + @VisibleForTesting + int setPreIdleTimeoutFactor(float ratio) { + if (!mDeepEnabled) { + if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Deep Idle disable"); + return SET_IDLE_FACTOR_RESULT_NOT_SUPPORT; + } else if (ratio <= MIN_PRE_IDLE_FACTOR_CHANGE) { + if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Invalid input"); + return SET_IDLE_FACTOR_RESULT_INVALID; + } else if (Math.abs(ratio - mPreIdleFactor) < MIN_PRE_IDLE_FACTOR_CHANGE) { + if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: New factor same as previous factor"); + return SET_IDLE_FACTOR_RESULT_IGNORED; + } + synchronized (this) { + mLastPreIdleFactor = mPreIdleFactor; + mPreIdleFactor = ratio; + } + if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: " + ratio); + postUpdatePreIdleFactor(); + return SET_IDLE_FACTOR_RESULT_OK; + } + + @VisibleForTesting + void resetPreIdleTimeoutMode() { + synchronized (this) { + mLastPreIdleFactor = mPreIdleFactor; + mPreIdleFactor = 1.0f; + } + if (DEBUG) Slog.d(TAG, "resetPreIdleTimeoutMode to 1.0"); + postResetPreIdleTimeoutFactor(); + } + + private void postUpdatePreIdleFactor() { + mHandler.sendEmptyMessage(MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR); + } + + private void postResetPreIdleTimeoutFactor() { + mHandler.sendEmptyMessage(MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR); + } + + @VisibleForTesting + void updatePreIdleFactor() { + synchronized (this) { + if (!shouldUseIdleTimeoutFactorLocked()) { + return; + } + if (mState == STATE_INACTIVE || mState == STATE_IDLE_PENDING) { + if (mNextAlarmTime == 0) { + return; + } + long delay = mNextAlarmTime - SystemClock.elapsedRealtime(); + if (delay < MIN_STATE_STEP_ALARM_CHANGE) { + return; + } + long newDelay = (long) (delay / mLastPreIdleFactor * mPreIdleFactor); + if (Math.abs(delay - newDelay) < MIN_STATE_STEP_ALARM_CHANGE) { + return; + } + scheduleAlarmLocked(newDelay, false); + } + } + } + + @VisibleForTesting + void maybeDoImmediateMaintenance() { + synchronized (this) { + if (mState == STATE_IDLE) { + long duration = SystemClock.elapsedRealtime() - mIdleStartTime; + /* Let's trgger a immediate maintenance, + * if it has been idle for a long time */ + if (duration > mConstants.IDLE_TIMEOUT) { + scheduleAlarmLocked(0, false); + } + } + } + } + + private boolean shouldUseIdleTimeoutFactorLocked() { + // exclude ACTIVE_REASON_MOTION, for exclude device in pocket case + if (mActiveReason == ACTIVE_REASON_MOTION) { + return false; + } + return true; + } + + /** Must only be used in tests. */ + @VisibleForTesting + void setIdleStartTimeForTest(long idleStartTime) { + synchronized (this) { + mIdleStartTime = idleStartTime; + } + } + void reportMaintenanceActivityIfNeededLocked() { boolean active = mJobsActive; if (active == mReportedMaintenanceActivity) { @@ -2945,6 +3173,11 @@ public class DeviceIdleController extends SystemService mHandler.sendMessage(msg); } + @VisibleForTesting + long getNextAlarmTime() { + return mNextAlarmTime; + } + boolean isOpsInactiveLocked() { return mActiveIdleOpCount <= 0 && !mJobsActive && !mAlarmsActive; } @@ -2994,6 +3227,7 @@ public class DeviceIdleController extends SystemService scheduleReportActiveLocked(type, Process.myUid()); addEvent(EVENT_NORMAL, type); } + mActiveReason = ACTIVE_REASON_MOTION; mState = STATE_ACTIVE; mInactiveTimeout = timeout; mCurIdleBudget = 0; @@ -3401,6 +3635,11 @@ public class DeviceIdleController extends SystemService + "and any [-d] is ignored"); pw.println(" motion"); pw.println(" Simulate a motion event to bring the device out of deep doze"); + pw.println(" pre-idle-factor [0|1|2]"); + pw.println(" Set a new factor to idle time before step to idle" + + "(inactive_to and idle_after_inactive_to)"); + pw.println(" reset-pre-idle-factor"); + pw.println(" Reset factor to idle time to default"); } class Shell extends ShellCommand { @@ -3571,6 +3810,7 @@ public class DeviceIdleController extends SystemService } } if (becomeActive) { + mActiveReason = ACTIVE_REASON_FORCED; becomeActiveLocked((arg == null ? "all" : arg) + "-disabled", Process.myUid()); } @@ -3820,6 +4060,52 @@ public class DeviceIdleController extends SystemService Binder.restoreCallingIdentity(token); } } + } else if ("pre-idle-factor".equals(cmd)) { + getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, + null); + synchronized (this) { + long token = Binder.clearCallingIdentity(); + int ret = SET_IDLE_FACTOR_RESULT_UNINIT; + try { + String arg = shell.getNextArg(); + boolean valid = false; + int mode = 0; + if (arg != null) { + mode = Integer.parseInt(arg); + ret = setPreIdleTimeoutMode(mode); + if (ret == SET_IDLE_FACTOR_RESULT_OK) { + pw.println("pre-idle-factor: " + mode); + valid = true; + } else if (ret == SET_IDLE_FACTOR_RESULT_NOT_SUPPORT) { + valid = true; + pw.println("Deep idle not supported"); + } else if (ret == SET_IDLE_FACTOR_RESULT_IGNORED) { + valid = true; + pw.println("Idle timeout factor not changed"); + } + } + if (!valid) { + pw.println("Unknown idle timeout factor: " + arg + + ",(error code: " + ret + ")"); + } + } catch (NumberFormatException e) { + pw.println("Unknown idle timeout factor" + + ",(error code: " + ret + ")"); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } else if ("reset-pre-idle-factor".equals(cmd)) { + getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, + null); + synchronized (this) { + long token = Binder.clearCallingIdentity(); + try { + resetPreIdleTimeoutMode(); + } finally { + Binder.restoreCallingIdentity(token); + } + } } else { return shell.handleDefaultCommands(cmd); } @@ -4053,6 +4339,9 @@ public class DeviceIdleController extends SystemService if (mAlarmsActive) { pw.print(" mAlarmsActive="); pw.println(mAlarmsActive); } + if (Math.abs(mPreIdleFactor - 1.0f) > MIN_PRE_IDLE_FACTOR_CHANGE) { + pw.print(" mPreIdleFactor="); pw.println(mPreIdleFactor); + } } } diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java index 1e9a00743a8b..190fff1f669c 100644 --- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java +++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java @@ -31,6 +31,19 @@ public interface PersistentDataBlockManagerInternal { */ byte[] getFrpCredentialHandle(); + /** Stores the data used to enable the Test Harness Mode after factory-resetting. */ + void setTestHarnessModeData(byte[] data); + + /** + * Retrieves the data used to place the device into Test Harness Mode. + * + * @throws IllegalStateException if the underlying storage is corrupt or inaccessible. + */ + byte[] getTestHarnessModeData(); + + /** Clear out the Test Harness Mode data. */ + void clearTestHarnessModeData(); + /** Update the OEM unlock enabled bit, bypassing user restriction checks. */ void forceOemUnlockEnabled(boolean enabled); } diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java index 21093b9f6f88..bd5ad960a886 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/PersistentDataBlockService.java @@ -16,6 +16,8 @@ package com.android.server; +import static com.android.internal.util.Preconditions.checkArgument; + import android.Manifest; import android.app.ActivityManager; import android.content.Context; @@ -28,12 +30,10 @@ import android.os.UserHandle; import android.os.UserManager; import android.service.persistentdata.IPersistentDataBlockService; import android.service.persistentdata.PersistentDataBlockManager; -import android.util.Log; import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.Preconditions; import libcore.io.IoUtils; @@ -65,6 +65,40 @@ import java.util.concurrent.TimeUnit; * * Clients can read any number of bytes from the currently written block up to its total size by * invoking {@link IPersistentDataBlockService#read} + * + * The persistent data block is currently laid out as follows: + * | ---------BEGINNING OF PARTITION-------------| + * | Partition digest (32 bytes) | + * | --------------------------------------------| + * | PARTITION_TYPE_MARKER (4 bytes) | + * | --------------------------------------------| + * | FRP data block length (4 bytes) | + * | --------------------------------------------| + * | FRP data (variable length) | + * | --------------------------------------------| + * | ... | + * | --------------------------------------------| + * | Test mode data block (10000 bytes) | + * | --------------------------------------------| + * | | Test mode data length (4 bytes) | + * | --------------------------------------------| + * | | Test mode data (variable length) | + * | | ... | + * | --------------------------------------------| + * | FRP credential handle block (1000 bytes) | + * | --------------------------------------------| + * | | FRP credential handle length (4 bytes)| + * | --------------------------------------------| + * | | FRP credential handle (variable len) | + * | | ... | + * | --------------------------------------------| + * | OEM Unlock bit (1 byte) | + * | ---------END OF PARTITION-------------------| + * + * TODO: now that the persistent partition contains several blocks, next time someone wants a new + * block, we should look at adding more generic block definitions and get rid of the various raw + * XXX_RESERVED_SIZE and XXX_DATA_SIZE constants. That will ensure the code is easier to maintain + * and less likely to introduce out-of-bounds read/write. */ public class PersistentDataBlockService extends SystemService { private static final String TAG = PersistentDataBlockService.class.getSimpleName(); @@ -73,10 +107,16 @@ public class PersistentDataBlockService extends SystemService { private static final int HEADER_SIZE = 8; // Magic number to mark block device as adhering to the format consumed by this service private static final int PARTITION_TYPE_MARKER = 0x19901873; - /** Size of the block reserved for FPR credential, including 4 bytes for the size header. */ + /** Size of the block reserved for FRP credential, including 4 bytes for the size header. */ private static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000; /** Maximum size of the FRP credential handle that can be stored. */ private static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4; + /** + * Size of the block reserved for Test Harness Mode data, including 4 bytes for the size header. + */ + private static final int TEST_MODE_RESERVED_SIZE = 10000; + /** Maximum size of the Test Harness Mode data that can be stored. */ + private static final int MAX_TEST_MODE_DATA_SIZE = TEST_MODE_RESERVED_SIZE - 4; // Limit to 100k as blocks larger than this might cause strain on Binder. private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100; @@ -221,6 +261,14 @@ public class PersistentDataBlockService extends SystemService { return mBlockDeviceSize; } + private long getFrpCredentialDataOffset() { + return getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE; + } + + private long getTestHarnessModeDataOffset() { + return getFrpCredentialDataOffset() - TEST_MODE_RESERVED_SIZE; + } + private boolean enforceChecksumValidity() { byte[] storedDigest = new byte[DIGEST_SIZE_BYTES]; @@ -383,7 +431,7 @@ public class PersistentDataBlockService extends SystemService { private long doGetMaximumDataBlockSize() { long actualSize = getBlockDeviceSize() - HEADER_SIZE - DIGEST_SIZE_BYTES - - FRP_CREDENTIAL_RESERVED_SIZE - 1; + - TEST_MODE_RESERVED_SIZE - FRP_CREDENTIAL_RESERVED_SIZE - 1; return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE; } @@ -391,6 +439,13 @@ public class PersistentDataBlockService extends SystemService { private native int nativeWipe(String path); private final IBinder mService = new IPersistentDataBlockService.Stub() { + + /** + * Write the data to the persistent data block. + * + * @return a positive integer of the number of bytes that were written if successful, + * otherwise a negative integer indicating there was a problem + */ @Override public int write(byte[] data) throws RemoteException { enforceUid(Binder.getCallingUid()); @@ -597,12 +652,51 @@ public class PersistentDataBlockService extends SystemService { @Override public void setFrpCredentialHandle(byte[] handle) { - Preconditions.checkArgument(handle == null || handle.length > 0, - "handle must be null or non-empty"); - Preconditions.checkArgument(handle == null - || handle.length <= MAX_FRP_CREDENTIAL_HANDLE_SIZE, - "handle must not be longer than " + MAX_FRP_CREDENTIAL_HANDLE_SIZE); + writeInternal(handle, getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE); + } + @Override + public byte[] getFrpCredentialHandle() { + return readInternal(getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE); + } + + @Override + public void setTestHarnessModeData(byte[] data) { + writeInternal(data, getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE); + } + + @Override + public byte[] getTestHarnessModeData() { + byte[] data = readInternal(getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE); + if (data == null) { + return new byte[0]; + } + return data; + } + + @Override + public void clearTestHarnessModeData() { + int size = Math.min(MAX_TEST_MODE_DATA_SIZE, getTestHarnessModeData().length) + 4; + writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size)); + } + + private void writeInternal(byte[] data, long offset, int dataLength) { + checkArgument(data == null || data.length > 0, "data must be null or non-empty"); + checkArgument( + data == null || data.length <= dataLength, + "data must not be longer than " + dataLength); + + ByteBuffer dataBuffer = ByteBuffer.allocate(dataLength + 4); + dataBuffer.putInt(data == null ? 0 : data.length); + if (data != null) { + dataBuffer.put(data); + } + dataBuffer.flip(); + + writeDataBuffer(offset, dataBuffer); + } + + private void writeDataBuffer(long offset, ByteBuffer dataBuffer) { FileOutputStream outputStream; try { outputStream = new FileOutputStream(new File(mDataBlockFile)); @@ -610,25 +704,15 @@ public class PersistentDataBlockService extends SystemService { Slog.e(TAG, "partition not available", e); return; } - - ByteBuffer data = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE); - data.putInt(handle == null ? 0 : handle.length); - if (handle != null) { - data.put(handle); - } - data.flip(); - synchronized (mLock) { if (!mIsWritable) { IoUtils.closeQuietly(outputStream); return; } - try { FileChannel channel = outputStream.getChannel(); - - channel.position(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE); - channel.write(data); + channel.position(offset); + channel.write(dataBuffer); outputStream.flush(); } catch (IOException e) { Slog.e(TAG, "unable to access persistent partition", e); @@ -641,8 +725,7 @@ public class PersistentDataBlockService extends SystemService { } } - @Override - public byte[] getFrpCredentialHandle() { + private byte[] readInternal(long offset, int maxLength) { if (!enforceChecksumValidity()) { throw new IllegalStateException("invalid checksum"); } @@ -652,14 +735,14 @@ public class PersistentDataBlockService extends SystemService { inputStream = new DataInputStream( new FileInputStream(new File(mDataBlockFile))); } catch (FileNotFoundException e) { - throw new IllegalStateException("frp partition not available"); + throw new IllegalStateException("persistent partition not available"); } try { synchronized (mLock) { - inputStream.skip(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE); + inputStream.skip(offset); int length = inputStream.readInt(); - if (length <= 0 || length > MAX_FRP_CREDENTIAL_HANDLE_SIZE) { + if (length <= 0 || length > maxLength) { return null; } byte[] bytes = new byte[length]; @@ -667,7 +750,7 @@ public class PersistentDataBlockService extends SystemService { return bytes; } } catch (IOException e) { - throw new IllegalStateException("frp handle not readable", e); + throw new IllegalStateException("persistent partition not readable", e); } finally { IoUtils.closeQuietly(inputStream); } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 7731c04a94b8..9d810cd8f3ca 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -1706,8 +1706,8 @@ class StorageManagerService extends IStorageManager.Stub int uid, String packageName, int[] ops) { long maxTime = 0; final List<AppOpsManager.PackageOps> pkgs = manager.getOpsForPackage(uid, packageName, ops); - for (AppOpsManager.PackageOps pkg : CollectionUtils.defeatNullable(pkgs)) { - for (AppOpsManager.OpEntry op : CollectionUtils.defeatNullable(pkg.getOps())) { + for (AppOpsManager.PackageOps pkg : CollectionUtils.emptyIfNull(pkgs)) { + for (AppOpsManager.OpEntry op : CollectionUtils.emptyIfNull(pkg.getOps())) { maxTime = Math.max(maxTime, op.getLastAccessTime()); } } diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java index fe27c495ef55..fd402cc08c0c 100644 --- a/services/core/java/com/android/server/am/AppCompactor.java +++ b/services/core/java/com/android/server/am/AppCompactor.java @@ -64,7 +64,7 @@ public final class AppCompactor { final private String COMPACT_ACTION_FILE = "file"; final private String COMPACT_ACTION_ANON = "anon"; - final private String COMPACT_ACTION_FULL = "full"; + final private String COMPACT_ACTION_FULL = "all"; final private String compactActionSome; final private String compactActionFull; diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 706fd55e2849..de389bc3aa01 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1223,11 +1223,13 @@ public class AudioService extends IAudioService.Stub private void checkMuteAffectedStreams() { // any stream with a min level > 0 is not muteable by definition - // STREAM_VOICE_CALL can be muted by applications that has the the MODIFY_PHONE_STATE permission. + // STREAM_VOICE_CALL and STREAM_BLUETOOTH_SCO can be muted by applications + // that has the the MODIFY_PHONE_STATE permission. for (int i = 0; i < mStreamStates.length; i++) { final VolumeStreamState vss = mStreamStates[i]; if (vss.mIndexMin > 0 && - vss.mStreamType != AudioSystem.STREAM_VOICE_CALL) { + (vss.mStreamType != AudioSystem.STREAM_VOICE_CALL && + vss.mStreamType != AudioSystem.STREAM_BLUETOOTH_SCO)) { mMuteAffectedStreams &= ~(1 << vss.mStreamType); } } @@ -1711,10 +1713,11 @@ public class AudioService extends IAudioService.Stub return; } - // If adjust is mute and the stream is STREAM_VOICE_CALL, make sure + // If adjust is mute and the stream is STREAM_VOICE_CALL or STREAM_BLUETOOTH_SCO, make sure // that the calling app have the MODIFY_PHONE_STATE permission. if (isMuteAdjust && - streamType == AudioSystem.STREAM_VOICE_CALL && + (streamType == AudioSystem.STREAM_VOICE_CALL || + streamType == AudioSystem.STREAM_BLUETOOTH_SCO) && mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { @@ -2038,12 +2041,14 @@ public class AudioService extends IAudioService.Stub + " CHANGE_ACCESSIBILITY_VOLUME callingPackage=" + callingPackage); return; } - if ((streamType == AudioManager.STREAM_VOICE_CALL) && + if ((streamType == AudioManager.STREAM_VOICE_CALL || + streamType == AudioManager.STREAM_BLUETOOTH_SCO) && (index == 0) && (mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED)) { - Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL and index 0 without" + Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL or" + + " STREAM_BLUETOOTH_SCO and index 0 without" + " MODIFY_PHONE_STATE callingPackage=" + callingPackage); return; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index d4b8eb2db007..944a95dda99b 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -18,7 +18,6 @@ package com.android.server.inputmethod; import android.annotation.NonNull; import android.annotation.UserIdInt; -import android.content.ComponentName; import android.view.inputmethod.InputMethodInfo; import com.android.server.LocalServices; @@ -42,11 +41,6 @@ public abstract class InputMethodManagerInternal { public abstract void hideCurrentInputMethod(); /** - * Switches to VR InputMethod defined in the packageName of {@param componentName}. - */ - public abstract void startVrInputMethodNoCheck(ComponentName componentName); - - /** * Returns the list of installed input methods for the specified user. * * @param userId The user ID to be queried. @@ -76,10 +70,6 @@ public abstract class InputMethodManagerInternal { } @Override - public void startVrInputMethodNoCheck(ComponentName componentName) { - } - - @Override public List<InputMethodInfo> getInputMethodListAsUser(int userId) { return Collections.emptyList(); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 52074a756968..eca371a78750 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -91,8 +91,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.UserManagerInternal; import android.provider.Settings; -import android.service.vr.IVrManager; -import android.service.vr.IVrStateCallbacks; import android.text.TextUtils; import android.text.style.SuggestionSpan; import android.util.ArrayMap; @@ -203,7 +201,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final int MSG_CREATE_SESSION = 1050; static final int MSG_START_INPUT = 2000; - static final int MSG_START_VR_INPUT = 2010; static final int MSG_UNBIND_CLIENT = 3000; static final int MSG_BIND_CLIENT = 3010; @@ -381,28 +378,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - /** - * VR state callback. - * Listens for when VR mode finishes. - */ - private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { - @Override - public void onVrStateChanged(boolean enabled) { - if (!enabled) { - restoreNonVrImeFromSettingsNoCheck(); - } - } - }; - - private void restoreNonVrImeFromSettingsNoCheck() { - // switch back to non-VR InputMethod from settings. - synchronized (mMethodMap) { - if (!mIsVrImeStarted) return; - mIsVrImeStarted = false; - updateFromSettingsLocked(false); - } - } - private static final class ClientDeathRecipient implements IBinder.DeathRecipient { private final InputMethodManagerService mImms; private final IInputMethodClient mClient; @@ -597,9 +572,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final ImeDisplayValidator mImeDisplayValidator; - /** True if VR IME started by {@link #startVrInputMethodNoCheck}. */ - boolean mIsVrImeStarted; - /** * If non-null, this is the input method service we are currently connected * to. @@ -977,31 +949,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } /** - * Start a VR InputMethod that matches IME with package name of {@param component}. - * Note: This method is called from {@link android.app.VrManager}. - */ - private void startVrInputMethodNoCheck(@Nullable ComponentName component) { - if (component == null) { - // clear the current VR-only IME (if any) and restore normal IME. - restoreNonVrImeFromSettingsNoCheck(); - return; - } - - synchronized (mMethodMap) { - String packageName = component.getPackageName(); - for (InputMethodInfo info : mMethodList) { - if (TextUtils.equals(info.getPackageName(), packageName) && info.isVrOnly()) { - // set this is as current inputMethod without updating settings. - setInputMethodEnabledLocked(info.getId(), true); - setInputMethodLocked(info.getId(), NOT_A_SUBTYPE_ID); - mIsVrImeStarted = true; - break; - } - } - } - } - - /** * Handles {@link Intent#ACTION_LOCALE_CHANGED}. * * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all @@ -1452,15 +1399,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId); mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( mSettings, context); - // Register VR-state listener. - IVrManager vrManager = (IVrManager) ServiceManager.getService(Context.VR_SERVICE); - if (vrManager != null) { - try { - vrManager.registerListener(mVrStateCallbacks); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to register VR mode state listener."); - } - } } private void resetDefaultImeLocked(Context context) { @@ -1688,25 +1626,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } final long ident = Binder.clearCallingIdentity(); try { - return getInputMethodListLocked(false /* isVrOnly */, resolvedUserIds[0]); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - - @Override - public List<InputMethodInfo> getVrInputMethodList() { - final int callingUserId = UserHandle.getCallingUserId(); - synchronized (mMethodMap) { - final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId, - mSettings.getCurrentUserId(), null); - if (resolvedUserIds.length != 1) { - return Collections.emptyList(); - } - final long ident = Binder.clearCallingIdentity(); - try { - return getInputMethodListLocked(true /* isVrOnly */, resolvedUserIds[0]); + return getInputMethodListLocked(resolvedUserIds[0]); } finally { Binder.restoreCallingIdentity(ident); } @@ -1732,8 +1652,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("mMethodMap") - private List<InputMethodInfo> getInputMethodListLocked(boolean isVrOnly, - @UserIdInt int userId) { + private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId) { final ArrayList<InputMethodInfo> methodList; if (userId == mSettings.getCurrentUserId()) { // Create a copy. @@ -1747,7 +1666,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, methodList); } - methodList.removeIf(imi -> imi.isVrOnly() != isVrOnly); return methodList; } @@ -2021,8 +1939,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } // Compute the final shown display ID with validated cs.selfReportedDisplayId for this // session & other conditions. - final int displayIdToShowIme = computeImeDisplayIdForTarget( - cs.selfReportedDisplayId, mIsVrImeStarted, mImeDisplayValidator); + final int displayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId, + mImeDisplayValidator); if (mCurClient != cs) { // Was the keyguard locked when switching over to the new client? @@ -2131,17 +2049,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * Find the display where the IME should be shown. * * @param displayId the ID of the display where the IME client target is. - * @param isVrImeStarted {@code true} if VR IME started, {@code false} otherwise. * @param checker instance of {@link ImeDisplayValidator} which is used for * checking display config to adjust the final target display. * @return The ID of the display where the IME should be shown. */ - static int computeImeDisplayIdForTarget(int displayId, boolean isVrImeStarted, - @NonNull ImeDisplayValidator checker) { - // For VR IME, we always show in default display. - if (isVrImeStarted) { - return DEFAULT_DISPLAY; - } + static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) { if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) { // We always assume that the default display id suitable to show the IME window. return DEFAULT_DISPLAY; @@ -3627,9 +3539,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case MSG_SET_INTERACTIVE: handleSetInteractive(msg.arg1 != 0); return true; - case MSG_START_VR_INPUT: - startVrInputMethodNoCheck((ComponentName) msg.obj); - return true; case MSG_REPORT_FULLSCREEN_MODE: { final boolean fullscreen = msg.arg1 != 0; final ClientState clientState = (ClientState)msg.obj; @@ -3716,6 +3625,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub try { final InputMethodInfo imi = new InputMethodInfo(context, ri, additionalSubtypeMap.get(imeId)); + if (imi.isVrOnly()) { + continue; // Skip VR-only IME, which isn't supported for now. + } methodList.add(imi); methodMap.put(imi.getId(), imi); if (DEBUG) { @@ -4099,17 +4011,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, boolean setSubtypeOnly) { - // Updates to InputMethod are transient in VR mode. Its not included in history. - final boolean isVrInput = imi != null && imi.isVrOnly(); - if (!isVrInput) { - // Update the history of InputMethod and Subtype - mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype); - } - - if (isVrInput) { - // Updates to InputMethod are transient in VR mode. Any changes to Settings are skipped. - return; - } + mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype); // Set Subtype here if (imi == null || subtypeId < 0) { @@ -4225,7 +4127,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) { synchronized (mMethodMap) { - return getInputMethodListLocked(false, userId); + return getInputMethodListLocked(userId); } } @@ -4257,11 +4159,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void startVrInputMethodNoCheck(@Nullable ComponentName componentName) { - mService.mHandler.obtainMessage(MSG_START_VR_INPUT, componentName).sendToTarget(); - } - - @Override public List<InputMethodInfo> getInputMethodListAsUser(int userId) { return mService.getInputMethodListAsUser(userId); } @@ -4648,7 +4545,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); for (int userId : userIds) { final List<InputMethodInfo> methods = all - ? getInputMethodListLocked(false, userId) + ? getInputMethodListLocked(userId) : getEnabledInputMethodListLocked(userId); if (userIds.length > 1) { pr.print("User #"); diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 6f0c5e83b4fe..a31b3b4b3b5a 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -159,11 +159,6 @@ public final class MultiClientInputMethodManagerService { } @Override - public void startVrInputMethodNoCheck(ComponentName componentName) { - reportNotSupported(); - } - - @Override public List<InputMethodInfo> getInputMethodListAsUser( @UserIdInt int userId) { return userIdToInputMethodInfoMapper.getAsList(userId); @@ -1244,13 +1239,6 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override - public List<InputMethodInfo> getVrInputMethodList() { - reportNotSupported(); - return Collections.emptyList(); - } - - @BinderThread - @Override public List<InputMethodInfo> getEnabledInputMethodList() { return mInputMethodInfoMap.getAsList(UserHandle.getUserId(Binder.getCallingUid())); } diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index cc0ac9a7e0a9..bd12075fdad3 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -443,6 +443,16 @@ public class JobSchedulerService extends com.android.server.SystemService "qc_window_size_rare_ms"; private static final String KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = "qc_max_execution_time_ms"; + private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = + "qc_max_job_count_active"; + private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = + "qc_max_job_count_working"; + private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = + "qc_max_job_count_frequent"; + private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = + "qc_max_job_count_rare"; + private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = + "qc_max_count_per_allowed_time"; private static final int DEFAULT_MIN_IDLE_COUNT = 1; private static final int DEFAULT_MIN_CHARGING_COUNT = 1; @@ -479,6 +489,15 @@ public class JobSchedulerService extends com.android.server.SystemService 24 * 60 * 60 * 1000L; // 24 hours private static final long DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 4 * 60 * 60 * 1000L; // 4 hours + private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = + 200; // 1200/hr + private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = + 1200; // 600/hr + private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = + 1800; // 225/hr + private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = + 2400; // 100/hr + private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 20; /** * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things @@ -682,6 +701,41 @@ public class JobSchedulerService extends com.android.server.SystemService public long QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS; + /** + * The maximum number of jobs an app can run within this particular standby bucket's + * window size. + */ + public int QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE; + + /** + * The maximum number of jobs an app can run within this particular standby bucket's + * window size. + */ + public int QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING; + + /** + * The maximum number of jobs an app can run within this particular standby bucket's + * window size. + */ + public int QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT; + + /** + * The maximum number of jobs an app can run within this particular standby bucket's + * window size. + */ + public int QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE; + + /** + * The maximum number of jobs that can run within the past + * {@link #QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}. + */ + public int QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME; + private final KeyValueListParser mParser = new KeyValueListParser(','); void updateConstantsLocked(String value) { @@ -767,6 +821,21 @@ public class JobSchedulerService extends com.android.server.SystemService QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS, DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS); + QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = mParser.getInt( + KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE, + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE); + QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = mParser.getInt( + KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING, + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING); + QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = mParser.getInt( + KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT, + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT); + QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = mParser.getInt( + KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE, + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE); + QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = mParser.getInt( + KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME, + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME); } void dump(IndentingPrintWriter pw) { @@ -823,6 +892,16 @@ public class JobSchedulerService extends com.android.server.SystemService QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS, QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE, + QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING, + QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT, + QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE, + QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME, + QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME).println(); pw.decreaseIndent(); } @@ -872,6 +951,16 @@ public class JobSchedulerService extends com.android.server.SystemService QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS); proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS, QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_ACTIVE, + QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_WORKING, + QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT, + QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE, + QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_ALLOWED_TIME, + QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME); proto.end(qcToken); proto.end(token); diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java index c16d1b4ecec5..5a0b991bc7de 100644 --- a/services/core/java/com/android/server/job/controllers/QuotaController.java +++ b/services/core/java/com/android/server/job/controllers/QuotaController.java @@ -230,10 +230,10 @@ public final class QuotaController extends StateController { @VisibleForTesting static class ExecutionStats { /** - * The time at which this record should be considered invalid, in the elapsed realtime - * timebase. + * The time after which this record should be considered invalid (out of date), in the + * elapsed realtime timebase. */ - public long invalidTimeElapsed; + public long expirationTimeElapsed; public long windowSizeMs; @@ -241,29 +241,45 @@ public final class QuotaController extends StateController { public long executionTimeInWindowMs; public int bgJobCountInWindow; - /** The total amount of time the app ran in the last {@link MAX_PERIOD_MS}. */ + /** The total amount of time the app ran in the last {@link #MAX_PERIOD_MS}. */ public long executionTimeInMaxPeriodMs; public int bgJobCountInMaxPeriod; /** - * The time after which the sum of all the app's sessions plus {@link mQuotaBufferMs} equals - * the quota. This is only valid if - * executionTimeInWindowMs >= {@link mAllowedTimePerPeriodMs} or - * executionTimeInMaxPeriodMs >= {@link mMaxExecutionTimeMs}. + * The time after which the sum of all the app's sessions plus {@link #mQuotaBufferMs} + * equals the quota. This is only valid if + * executionTimeInWindowMs >= {@link #mAllowedTimePerPeriodMs} or + * executionTimeInMaxPeriodMs >= {@link #mMaxExecutionTimeMs}. */ public long quotaCutoffTimeElapsed; + /** + * The time after which {@link #jobCountInAllowedTime} should be considered invalid, in the + * elapsed realtime timebase. + */ + public long jobCountExpirationTimeElapsed; + + /** + * The number of jobs that ran in at least the last {@link #mAllowedTimePerPeriodMs}. + * It may contain a few stale entries since cleanup won't happen exactly every + * {@link #mAllowedTimePerPeriodMs}. + */ + public int jobCountInAllowedTime; + @Override public String toString() { return new StringBuilder() - .append("invalidTime=").append(invalidTimeElapsed).append(", ") + .append("expirationTime=").append(expirationTimeElapsed).append(", ") .append("windowSize=").append(windowSizeMs).append(", ") .append("executionTimeInWindow=").append(executionTimeInWindowMs).append(", ") .append("bgJobCountInWindow=").append(bgJobCountInWindow).append(", ") .append("executionTimeInMaxPeriod=").append(executionTimeInMaxPeriodMs) .append(", ") .append("bgJobCountInMaxPeriod=").append(bgJobCountInMaxPeriod).append(", ") - .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed) + .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed).append(", ") + .append("jobCountExpirationTime").append(jobCountExpirationTimeElapsed) + .append(", ") + .append("jobCountInAllowedTime").append(jobCountInAllowedTime) .toString(); } @@ -271,13 +287,15 @@ public final class QuotaController extends StateController { public boolean equals(Object obj) { if (obj instanceof ExecutionStats) { ExecutionStats other = (ExecutionStats) obj; - return this.invalidTimeElapsed == other.invalidTimeElapsed + return this.expirationTimeElapsed == other.expirationTimeElapsed && this.windowSizeMs == other.windowSizeMs && this.executionTimeInWindowMs == other.executionTimeInWindowMs && this.bgJobCountInWindow == other.bgJobCountInWindow && this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs && this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod - && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed; + && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed + && this.jobCountExpirationTimeElapsed == other.jobCountExpirationTimeElapsed + && this.jobCountInAllowedTime == other.jobCountInAllowedTime; } else { return false; } @@ -286,13 +304,15 @@ public final class QuotaController extends StateController { @Override public int hashCode() { int result = 0; - result = 31 * result + hashLong(invalidTimeElapsed); + result = 31 * result + hashLong(expirationTimeElapsed); result = 31 * result + hashLong(windowSizeMs); result = 31 * result + hashLong(executionTimeInWindowMs); result = 31 * result + bgJobCountInWindow; result = 31 * result + hashLong(executionTimeInMaxPeriodMs); result = 31 * result + bgJobCountInMaxPeriod; result = 31 * result + hashLong(quotaCutoffTimeElapsed); + result = 31 * result + hashLong(jobCountExpirationTimeElapsed); + result = 31 * result + jobCountInAllowedTime; return result; } } @@ -320,7 +340,7 @@ public final class QuotaController extends StateController { /** * List of jobs that started while the UID was in the TOP state. There will be no more than - * 16 ({@link JobSchedulerService.MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is + * 16 ({@link JobSchedulerService#MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is * fine. */ private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>(); @@ -343,7 +363,7 @@ public final class QuotaController extends StateController { private long mAllowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; /** - * The maximum amount of time an app can have its jobs running within a {@link MAX_PERIOD_MS} + * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS} * window. */ private long mMaxExecutionTimeMs = 4 * 60 * MINUTE_IN_MILLIS; @@ -355,17 +375,20 @@ public final class QuotaController extends StateController { private long mQuotaBufferMs = 30 * 1000L; // 30 seconds /** - * {@link mAllowedTimePerPeriodMs} - {@link mQuotaBufferMs}. This can be used to determine when - * an app will have enough quota to transition from out-of-quota to in-quota. + * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine + * when an app will have enough quota to transition from out-of-quota to in-quota. */ private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs; /** - * {@link mMaxExecutionTimeMs} - {@link mQuotaBufferMs}. This can be used to determine when an + * {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an * app will have enough quota to transition from out-of-quota to in-quota. */ private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; + /** The maximum number of jobs that can run within the past {@link #mAllowedTimePerPeriodMs}. */ + private int mMaxJobCountPerAllowedTime = 20; + private long mNextCleanupTimeElapsed = 0; private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener = new AlarmManager.OnAlarmListener() { @@ -412,6 +435,23 @@ public final class QuotaController extends StateController { /** The maximum period any bucket can have. */ private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS; + /** + * The maximum number of jobs based on its standby bucket. For each max value count in the + * array, the app will not be allowed to run more than that many number of jobs within the + * latest time interval of its rolling window size. + * + * @see #mBucketPeriodsMs + */ + private final int[] mMaxBucketJobCounts = new int[] { + 200, // ACTIVE -- 1200/hr + 1200, // WORKING -- 600/hr + 1800, // FREQUENT -- 225/hr + 2400 // RARE -- 100/hr + }; + + /** The minimum number of jobs that any bucket will be allowed to run. */ + private static final int MIN_BUCKET_JOB_COUNT = 100; + /** An app has reached its quota. The message should contain a {@link Package} object. */ private static final int MSG_REACHED_QUOTA = 0; /** Drop any old timing sessions. */ @@ -463,17 +503,21 @@ public final class QuotaController extends StateController { @Override public void prepareForExecutionLocked(JobStatus jobStatus) { if (DEBUG) Slog.d(TAG, "Prepping for " + jobStatus.toShortString()); + + final int uid = jobStatus.getSourceUid(); + if (mActivityManagerInternal.getUidProcessState(uid) <= ActivityManager.PROCESS_STATE_TOP) { + mTopStartedJobs.add(jobStatus); + // Top jobs won't count towards quota so there's no need to involve the Timer. + return; + } + final int userId = jobStatus.getSourceUserId(); final String packageName = jobStatus.getSourcePackageName(); - final int uid = jobStatus.getSourceUid(); Timer timer = mPkgTimers.get(userId, packageName); if (timer == null) { timer = new Timer(uid, userId, packageName); mPkgTimers.add(userId, packageName, timer); } - if (mActivityManagerInternal.getUidProcessState(uid) == ActivityManager.PROCESS_STATE_TOP) { - mTopStartedJobs.add(jobStatus); - } timer.startTrackingJob(jobStatus); } @@ -548,6 +592,36 @@ public final class QuotaController extends StateController { mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; changed = true; } + int newMaxCountPerAllowedPeriod = Math.max(10, + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME); + if (mMaxJobCountPerAllowedTime != newMaxCountPerAllowedPeriod) { + mMaxJobCountPerAllowedTime = newMaxCountPerAllowedPeriod; + changed = true; + } + int newActiveMaxJobCount = Math.max(mMaxJobCountPerAllowedTime, + Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE)); + if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) { + mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount; + changed = true; + } + int newWorkingMaxJobCount = Math.max(mMaxJobCountPerAllowedTime, + Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING)); + if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) { + mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount; + changed = true; + } + int newFrequentMaxJobCount = Math.max(mMaxJobCountPerAllowedTime, + Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT)); + if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) { + mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount; + changed = true; + } + int newRareMaxJobCount = Math.max(mMaxJobCountPerAllowedTime, + Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE)); + if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) { + mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount; + changed = true; + } if (changed) { // Update job bookkeeping out of band. @@ -631,18 +705,39 @@ public final class QuotaController extends StateController { return isTopStartedJob(jobStatus) || isUidInForeground(jobStatus.getSourceUid()) || isWithinQuotaLocked( - jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); + jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); } - private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName, + @VisibleForTesting + boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName, final int standbyBucket) { if (standbyBucket == NEVER_INDEX) return false; // This check is needed in case the flag is toggled after a job has been registered. if (!mShouldThrottle) return true; // Quota constraint is not enforced while charging or when parole is on. - return mChargeTracker.isCharging() || mInParole - || getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) > 0; + if (mChargeTracker.isCharging() || mInParole) { + return true; + } + + return getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) > 0 + && isUnderJobCountQuotaLocked(userId, packageName, standbyBucket); + } + + private boolean isUnderJobCountQuotaLocked(final int userId, @NonNull final String packageName, + final int standbyBucket) { + ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket, false); + return isUnderJobCountQuotaLocked(stats, standbyBucket); + } + + private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats, + final int standbyBucket) { + final long now = sElapsedRealtimeClock.millis(); + final boolean isUnderAllowedTimeQuota = + (stats.jobCountExpirationTimeElapsed <= now + || stats.jobCountInAllowedTime < mMaxJobCountPerAllowedTime); + return isUnderAllowedTimeQuota + && (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]); } @VisibleForTesting @@ -679,6 +774,13 @@ public final class QuotaController extends StateController { @NonNull ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName, final int standbyBucket) { + return getExecutionStatsLocked(userId, packageName, standbyBucket, true); + } + + @NonNull + private ExecutionStats getExecutionStatsLocked(final int userId, + @NonNull final String packageName, final int standbyBucket, + final boolean refreshStatsIfOld) { if (standbyBucket == NEVER_INDEX) { Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app."); return new ExecutionStats(); @@ -693,14 +795,16 @@ public final class QuotaController extends StateController { stats = new ExecutionStats(); appStats[standbyBucket] = stats; } - final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket]; - Timer timer = mPkgTimers.get(userId, packageName); - if ((timer != null && timer.isActive()) - || stats.invalidTimeElapsed <= sElapsedRealtimeClock.millis() - || stats.windowSizeMs != bucketWindowSizeMs) { - // The stats are no longer valid. - stats.windowSizeMs = bucketWindowSizeMs; - updateExecutionStatsLocked(userId, packageName, stats); + if (refreshStatsIfOld) { + final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket]; + Timer timer = mPkgTimers.get(userId, packageName); + if ((timer != null && timer.isActive()) + || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis() + || stats.windowSizeMs != bucketWindowSizeMs) { + // The stats are no longer valid. + stats.windowSizeMs = bucketWindowSizeMs; + updateExecutionStatsLocked(userId, packageName, stats); + } } return stats; @@ -717,14 +821,14 @@ public final class QuotaController extends StateController { Timer timer = mPkgTimers.get(userId, packageName); final long nowElapsed = sElapsedRealtimeClock.millis(); - stats.invalidTimeElapsed = nowElapsed + MAX_PERIOD_MS; + stats.expirationTimeElapsed = nowElapsed + MAX_PERIOD_MS; if (timer != null && timer.isActive()) { stats.executionTimeInWindowMs = stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed); stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount(); // If the timer is active, the value will be stale at the next method call, so // invalidate now. - stats.invalidTimeElapsed = nowElapsed; + stats.expirationTimeElapsed = nowElapsed; if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) { stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed, nowElapsed - mAllowedTimeIntoQuotaMs); @@ -800,7 +904,7 @@ public final class QuotaController extends StateController { break; } } - stats.invalidTimeElapsed = nowElapsed + emptyTimeMs; + stats.expirationTimeElapsed = nowElapsed + emptyTimeMs; } private void invalidateAllExecutionStatsLocked(final int userId, @@ -811,13 +915,35 @@ public final class QuotaController extends StateController { for (int i = 0; i < appStats.length; ++i) { ExecutionStats stats = appStats[i]; if (stats != null) { - stats.invalidTimeElapsed = nowElapsed; + stats.expirationTimeElapsed = nowElapsed; } } } } @VisibleForTesting + void incrementJobCount(final int userId, @NonNull final String packageName, int count) { + final long now = sElapsedRealtimeClock.millis(); + ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); + if (appStats == null) { + appStats = new ExecutionStats[mBucketPeriodsMs.length]; + mExecutionStatsCache.add(userId, packageName, appStats); + } + for (int i = 0; i < appStats.length; ++i) { + ExecutionStats stats = appStats[i]; + if (stats == null) { + stats = new ExecutionStats(); + appStats[i] = stats; + } + if (stats.jobCountExpirationTimeElapsed <= now) { + stats.jobCountExpirationTimeElapsed = now + mAllowedTimePerPeriodMs; + stats.jobCountInAllowedTime = 0; + } + stats.jobCountInAllowedTime += count; + } + } + + @VisibleForTesting void saveTimingSession(final int userId, @NonNull final String packageName, @NonNull final TimingSession session) { synchronized (mLock) { @@ -1023,9 +1149,12 @@ public final class QuotaController extends StateController { final String pkgString = string(userId, packageName); ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); + final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket); + QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName); if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs - && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs) { + && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs + && isUnderJobCountQuota) { // Already in quota. Why was this method called? if (DEBUG) { Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString @@ -1042,18 +1171,22 @@ public final class QuotaController extends StateController { mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget(); return; } + if (alarmListener == null) { alarmListener = new QcAlarmListener(userId, packageName); mInQuotaAlarmListeners.add(userId, packageName, alarmListener); } // The time this app will have quota again. - long inQuotaTimeElapsed = - stats.quotaCutoffTimeElapsed + stats.windowSizeMs; + long inQuotaTimeElapsed = stats.quotaCutoffTimeElapsed + stats.windowSizeMs; if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeMs) { inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed, stats.quotaCutoffTimeElapsed + MAX_PERIOD_MS); } + if (!isUnderJobCountQuota) { + inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed, + stats.jobCountExpirationTimeElapsed + mAllowedTimePerPeriodMs); + } // Only schedule the alarm if: // 1. There isn't one currently scheduled // 2. The new alarm is significantly earlier than the previous alarm (which could be the @@ -1228,6 +1361,7 @@ public final class QuotaController extends StateController { mRunningBgJobs.add(jobStatus); if (shouldTrackLocked()) { mBgJobCount++; + incrementJobCount(mPkg.userId, mPkg.packageName, 1); if (mRunningBgJobs.size() == 1) { // Started tracking the first job. mStartTimeElapsed = sElapsedRealtimeClock.millis(); @@ -1324,6 +1458,7 @@ public final class QuotaController extends StateController { // repeatedly plugged in and unplugged, or an app changes foreground state // very frequently, the job count for a package may be artificially high. mBgJobCount = mRunningBgJobs.size(); + incrementJobCount(mPkg.userId, mPkg.packageName, mBgJobCount); // Starting the timer means that all cached execution stats are now // incorrect. invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName); @@ -1604,6 +1739,12 @@ public final class QuotaController extends StateController { @VisibleForTesting @NonNull + int[] getBucketMaxJobCounts() { + return mMaxBucketJobCounts; + } + + @VisibleForTesting + @NonNull long[] getBucketWindowSizes() { return mBucketPeriodsMs; } @@ -1631,6 +1772,11 @@ public final class QuotaController extends StateController { } @VisibleForTesting + int getMaxJobCountPerAllowedTime() { + return mMaxJobCountPerAllowedTime; + } + + @VisibleForTesting @Nullable List<TimingSession> getTimingSessions(int userId, String packageName) { return mTimingSessions.get(userId, packageName); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 8734ceb614a9..a9ae74f67de7 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -947,6 +947,10 @@ public class LockSettingsService extends ILockSettings.Stub { public void setSeparateProfileChallengeEnabled(int userId, boolean enabled, String managedUserPassword) { checkWritePermission(userId); + if (!mLockPatternUtils.hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires secure lock screen feature."); + } synchronized (mSeparateChallengeLock) { setSeparateProfileChallengeEnabledLocked(userId, enabled, managedUserPassword); } @@ -1305,6 +1309,10 @@ public class LockSettingsService extends ILockSettings.Stub { public void setLockCredential(String credential, int type, String savedCredential, int requestedQuality, int userId) throws RemoteException { + if (!mLockPatternUtils.hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires secure lock screen feature"); + } checkWritePermission(userId); synchronized (mSeparateChallengeLock) { setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId); @@ -2906,6 +2914,10 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, byte[] token, int requestedQuality, int userId) { + if (!mLockPatternUtils.hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires secure lock screen feature."); + } try { return LockSettingsService.this.setLockCredentialWithToken(credential, type, tokenHandle, token, requestedQuality, userId); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java index 07f23ce2231a..6163077e1acf 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java @@ -18,6 +18,7 @@ package com.android.server.locksettings; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; + import static com.android.internal.widget.LockPatternUtils.stringToPattern; import android.app.ActivityManager; @@ -58,6 +59,18 @@ class LockSettingsShellCommand extends ShellCommand { mCurrentUserId = ActivityManager.getService().getCurrentUser().id; parseArgs(); + if (!mLockPatternUtils.hasSecureLockScreen()) { + switch (cmd) { + case COMMAND_HELP: + case COMMAND_GET_DISABLED: + case COMMAND_SET_DISABLED: + break; + default: + getErrPrintWriter().println( + "The device does not support lock screen - ignoring the command."); + return -1; + } + } if (!checkCredential()) { return -1; } diff --git a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java index 2ae424dd4b1b..5b765dfee3a4 100644 --- a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java +++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java @@ -22,63 +22,117 @@ import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; +import android.os.Process; import android.os.ServiceManager; +import android.util.ByteStringUtils; +import android.util.EventLog; import android.util.Log; import com.android.server.pm.dex.DexLogger; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** - * Scheduled job to trigger logging of app dynamic code loading. This runs daily while idle and - * charging. The actual logging is performed by {@link DexLogger}. + * Scheduled jobs related to logging of app dynamic code loading. The idle logging job runs daily + * while idle and charging and calls {@link DexLogger} to write dynamic code information to the + * event log. The audit watching job scans the event log periodically while idle to find AVC audit + * messages indicating use of dynamic native code and adds the information to {@link DexLogger}. * {@hide} */ public class DynamicCodeLoggingService extends JobService { private static final String TAG = DynamicCodeLoggingService.class.getName(); - private static final int JOB_ID = 2030028; - private static final long PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1); + private static final boolean DEBUG = false; - private volatile boolean mStopRequested = false; + private static final int IDLE_LOGGING_JOB_ID = 2030028; + private static final int AUDIT_WATCHING_JOB_ID = 203142925; - private static final boolean DEBUG = false; + private static final long IDLE_LOGGING_PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1); + private static final long AUDIT_WATCHING_PERIOD_MILLIS = TimeUnit.HOURS.toMillis(2); + + private static final int AUDIT_AVC = 1400; // Defined in linux/audit.h + private static final String AVC_PREFIX = "type=" + AUDIT_AVC + " "; + + private static final Pattern EXECUTE_NATIVE_AUDIT_PATTERN = + Pattern.compile(".*\\bavc: granted \\{ execute(?:_no_trans|) \\} .*" + + "\\bpath=(?:\"([^\" ]*)\"|([0-9A-F]+)) .*" + + "\\bscontext=u:r:untrusted_app_2(?:5|7):.*" + + "\\btcontext=u:object_r:app_data_file:.*" + + "\\btclass=file\\b.*"); + + private volatile boolean mIdleLoggingStopRequested = false; + private volatile boolean mAuditWatchingStopRequested = false; /** - * Schedule our job with the {@link JobScheduler}. + * Schedule our jobs with the {@link JobScheduler}. */ public static void schedule(Context context) { ComponentName serviceName = new ComponentName( "android", DynamicCodeLoggingService.class.getName()); JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - js.schedule(new JobInfo.Builder(JOB_ID, serviceName) + js.schedule(new JobInfo.Builder(IDLE_LOGGING_JOB_ID, serviceName) .setRequiresDeviceIdle(true) .setRequiresCharging(true) - .setPeriodic(PERIOD_MILLIS) + .setPeriodic(IDLE_LOGGING_PERIOD_MILLIS) .build()); + js.schedule(new JobInfo.Builder(AUDIT_WATCHING_JOB_ID, serviceName) + .setRequiresDeviceIdle(true) + .setRequiresBatteryNotLow(true) + .setPeriodic(AUDIT_WATCHING_PERIOD_MILLIS) + .build()); + if (DEBUG) { - Log.d(TAG, "Job scheduled"); + Log.d(TAG, "Jobs scheduled"); } } @Override public boolean onStartJob(JobParameters params) { + int jobId = params.getJobId(); if (DEBUG) { - Log.d(TAG, "onStartJob"); + Log.d(TAG, "onStartJob " + jobId); + } + switch (jobId) { + case IDLE_LOGGING_JOB_ID: + mIdleLoggingStopRequested = false; + new IdleLoggingThread(params).start(); + return true; // Job is running on another thread + case AUDIT_WATCHING_JOB_ID: + mAuditWatchingStopRequested = false; + new AuditWatchingThread(params).start(); + return true; // Job is running on another thread + default: + // Shouldn't happen, but indicate nothing is running. + return false; } - mStopRequested = false; - new IdleLoggingThread(params).start(); - return true; // Job is running on another thread } @Override public boolean onStopJob(JobParameters params) { + int jobId = params.getJobId(); if (DEBUG) { - Log.d(TAG, "onStopJob"); + Log.d(TAG, "onStopJob " + jobId); } - mStopRequested = true; - return true; // Requests job be re-scheduled. + switch (jobId) { + case IDLE_LOGGING_JOB_ID: + mIdleLoggingStopRequested = true; + return true; // Requests job be re-scheduled. + case AUDIT_WATCHING_JOB_ID: + mAuditWatchingStopRequested = true; + return true; // Requests job be re-scheduled. + default: + return false; + } + } + + private static DexLogger getDexLogger() { + PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package"); + return pm.getDexManager().getDexLogger(); } private class IdleLoggingThread extends Thread { @@ -92,14 +146,13 @@ public class DynamicCodeLoggingService extends JobService { @Override public void run() { if (DEBUG) { - Log.d(TAG, "Starting logging run"); + Log.d(TAG, "Starting IdleLoggingJob run"); } - PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package"); - DexLogger dexLogger = pm.getDexManager().getDexLogger(); + DexLogger dexLogger = getDexLogger(); for (String packageName : dexLogger.getAllPackagesWithDynamicCodeLoading()) { - if (mStopRequested) { - Log.w(TAG, "Stopping logging run at scheduler request"); + if (mIdleLoggingStopRequested) { + Log.w(TAG, "Stopping IdleLoggingJob run at scheduler request"); return; } @@ -108,8 +161,128 @@ public class DynamicCodeLoggingService extends JobService { jobFinished(mParams, /* reschedule */ false); if (DEBUG) { - Log.d(TAG, "Finished logging run"); + Log.d(TAG, "Finished IdleLoggingJob run"); } } } + + private class AuditWatchingThread extends Thread { + private final JobParameters mParams; + + AuditWatchingThread(JobParameters params) { + super("DynamicCodeLoggingService_AuditWatchingJob"); + mParams = params; + } + + @Override + public void run() { + if (DEBUG) { + Log.d(TAG, "Starting AuditWatchingJob run"); + } + + if (processAuditEvents()) { + jobFinished(mParams, /* reschedule */ false); + if (DEBUG) { + Log.d(TAG, "Finished AuditWatchingJob run"); + } + } + } + + private boolean processAuditEvents() { + // Scan the event log for SELinux (avc) audit messages indicating when an + // (untrusted) app has executed native code from an app data + // file. Matches are recorded in DexLogger. + // + // These messages come from the kernel audit system via logd. (Note that + // some devices may not generate these messages at all, or the format may + // be different, in which case nothing will be recorded.) + // + // The messages use the auditd tag and the uid of the app that executed + // the code. + // + // A typical message might look like this: + // type=1400 audit(0.0:521): avc: granted { execute } for comm="executable" + // path="/data/data/com.dummy.app/executable" dev="sda13" ino=1655302 + // scontext=u:r:untrusted_app_27:s0:c66,c257,c512,c768 + // tcontext=u:object_r:app_data_file:s0:c66,c257,c512,c768 tclass=file + // + // The information we want is the uid and the path. (Note this may be + // either a quoted string, as shown above, or a sequence of hex-encoded + // bytes.) + // + // On each run we process all the matching events in the log. This may + // mean re-processing events we have already seen, and in any case there + // may be duplicate events for the same app+file. These are de-duplicated + // by DexLogger. + // + // Note that any app can write a message to the event log, including one + // that looks exactly like an AVC audit message, so the information may + // be spoofed by an app; in such a case the uid we see will be the app + // that generated the spoof message. + + try { + int[] tags = { EventLog.getTagCode("auditd") }; + if (tags[0] == -1) { + // auditd is not a registered tag on this system, so there can't be any messages + // of interest. + return true; + } + + DexLogger dexLogger = getDexLogger(); + + List<EventLog.Event> events = new ArrayList<>(); + EventLog.readEvents(tags, events); + + for (int i = 0; i < events.size(); ++i) { + if (mAuditWatchingStopRequested) { + Log.w(TAG, "Stopping AuditWatchingJob run at scheduler request"); + return false; + } + + EventLog.Event event = events.get(i); + + // Discard clearly unrelated messages as quickly as we can. + int uid = event.getUid(); + if (!Process.isApplicationUid(uid)) { + continue; + } + Object data = event.getData(); + if (!(data instanceof String)) { + continue; + } + String message = (String) data; + if (!message.startsWith(AVC_PREFIX)) { + continue; + } + + // And then use a regular expression to verify it's one of the messages we're + // interested in and to extract the path of the file being loaded. + Matcher matcher = EXECUTE_NATIVE_AUDIT_PATTERN.matcher(message); + if (!matcher.matches()) { + continue; + } + String path = matcher.group(1); + if (path == null) { + // If the path contains spaces or various weird characters the kernel + // hex-encodes the bytes; we need to undo that. + path = unhex(matcher.group(2)); + } + dexLogger.recordNative(uid, path); + } + + return true; + } catch (Exception e) { + Log.e(TAG, "AuditWatchingJob failed", e); + return true; + } + } + } + + private static String unhex(String hexEncodedPath) { + byte[] bytes = ByteStringUtils.fromHexToByteArray(hexEncodedPath); + if (bytes == null || bytes.length == 0) { + return ""; + } + return new String(bytes); + } } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 8a6105cb7fa9..efafdfaf2b54 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -611,6 +611,31 @@ public class Installer extends SystemService { } } + public boolean snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags) + throws InstallerException { + if (!checkBeforeRemote()) return false; + + try { + mInstalld.snapshotAppData(null, pkg, userId, storageFlags); + return true; + } catch (Exception e) { + throw InstallerException.from(e); + } + } + + public boolean restoreAppDataSnapshot(String pkg, @AppIdInt int appId, long ceDataInode, + String seInfo, @UserIdInt int userId, int storageFlags) throws InstallerException { + if (!checkBeforeRemote()) return false; + + try { + mInstalld.restoreAppDataSnapshot(null, pkg, appId, ceDataInode, seInfo, userId, + storageFlags); + return true; + } catch (Exception e) { + throw InstallerException.from(e); + } + } + private static void assertValidInstructionSet(String instructionSet) throws InstallerException { for (String abi : Build.SUPPORTED_ABIS) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 09fe26d19019..b8342cf8f81e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -42,6 +42,7 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRAD import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; +import static android.content.pm.PackageManager.INSTALL_ALLOW_DOWNGRADE; import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE; import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION; @@ -201,6 +202,7 @@ import android.content.pm.VersionedPackage; import android.content.pm.dex.ArtManager; import android.content.pm.dex.DexMetadataHelper; import android.content.pm.dex.IArtManager; +import android.content.rollback.IRollbackManager; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Bitmap; @@ -13872,6 +13874,38 @@ public class PackageManagerService extends IPackageManager.Stub } } + // If this is an update to a package that might be potentially downgraded, then we + // need to check with the rollback manager whether there's any userdata that might + // need to be restored for the package. + // + // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL. + if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) { + IRollbackManager rm = IRollbackManager.Stub.asInterface( + ServiceManager.getService(Context.ROLLBACK_SERVICE)); + + final String packageName = res.pkg.applicationInfo.packageName; + final String seInfo = res.pkg.applicationInfo.seInfo; + final PackageSetting ps; + int appId = -1; + long ceDataInode = -1; + synchronized (mSettings) { + ps = mSettings.getPackageLPr(packageName); + if (ps != null) { + appId = ps.appId; + ceDataInode = ps.getCeDataInode(userId); + } + } + + if (ps != null) { + try { + rm.restoreUserData(packageName, userId, appId, ceDataInode, seInfo, token); + } catch (RemoteException re) { + // Cannot happen, the RollbackManager is hosted in the same process. + } + doRestore = true; + } + } + if (!doRestore) { // No restore possible, or the Backup Manager was mysteriously not // available -- just fire the post-install work request directly. @@ -14569,6 +14603,9 @@ public class PackageManagerService extends IPackageManager.Stub enableRollbackIntent.putExtra( PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS, installFlags); + enableRollbackIntent.putExtra( + PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS, + resolveUserIds(args.user.getIdentifier())); enableRollbackIntent.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)), PACKAGE_MIME_TYPE); enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); @@ -23791,6 +23828,11 @@ public class PackageManagerService extends IPackageManager.Stub } return mArtManagerService.compileLayouts(pkg); } + + @Override + public void finishPackageInstall(int token, boolean didLaunch) { + PackageManagerService.this.finishPackageInstall(token, didLaunch); + } } @GuardedBy("mPackages") diff --git a/services/core/java/com/android/server/pm/dex/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java index 78fa82c6bcdd..59cc0cfeef45 100644 --- a/services/core/java/com/android/server/pm/dex/DexLogger.java +++ b/services/core/java/com/android/server/pm/dex/DexLogger.java @@ -16,11 +16,15 @@ package com.android.server.pm.dex; +import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX; +import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_NATIVE; + import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.os.FileUtils; import android.os.RemoteException; +import android.os.UserHandle; import android.os.storage.StorageManager; import android.util.ByteStringUtils; import android.util.EventLog; @@ -35,20 +39,23 @@ import com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile; import com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode; import java.io.File; +import java.io.IOException; import java.util.Map; import java.util.Set; /** - * This class is responsible for logging data about secondary dex files. - * The data logged includes hashes of the name and content of each file. + * This class is responsible for logging data about secondary dex files and, despite the name, + * native code executed from an app's private directory. The data logged includes hashes of the + * name and content of each file. */ public class DexLogger { private static final String TAG = "DexLogger"; - // Event log tag & subtag used for SafetyNet logging of dynamic - // code loading (DCL) - see b/63927552. + // Event log tag & subtags used for SafetyNet logging of dynamic code loading (DCL) - + // see b/63927552. private static final int SNET_TAG = 0x534e4554; - private static final String DCL_SUBTAG = "dcl"; + private static final String DCL_DEX_SUBTAG = "dcl"; + private static final String DCL_NATIVE_SUBTAG = "dcln"; private final IPackageManager mPackageManager; private final PackageDynamicCodeLoading mPackageDynamicCodeLoading; @@ -114,12 +121,11 @@ public class DexLogger { } int storageFlags; - if (appInfo.deviceProtectedDataDir != null - && FileUtils.contains(appInfo.deviceProtectedDataDir, filePath)) { - storageFlags = StorageManager.FLAG_STORAGE_DE; - } else if (appInfo.credentialProtectedDataDir != null - && FileUtils.contains(appInfo.credentialProtectedDataDir, filePath)) { + + if (fileIsUnder(filePath, appInfo.credentialProtectedDataDir)) { storageFlags = StorageManager.FLAG_STORAGE_CE; + } else if (fileIsUnder(filePath, appInfo.deviceProtectedDataDir)) { + storageFlags = StorageManager.FLAG_STORAGE_DE; } else { Slog.e(TAG, "Could not infer CE/DE storage for path " + filePath); needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId); @@ -139,6 +145,9 @@ public class DexLogger { + ": " + e.getMessage()); } + String subtag = fileInfo.mFileType == FILE_TYPE_DEX + ? DCL_DEX_SUBTAG + : DCL_NATIVE_SUBTAG; String fileName = new File(filePath).getName(); String message = PackageUtils.computeSha256Digest(fileName.getBytes()); @@ -165,7 +174,7 @@ public class DexLogger { } if (loadingUid != -1) { - writeDclEvent(loadingUid, message); + writeDclEvent(subtag, loadingUid, message); } } } @@ -175,21 +184,58 @@ public class DexLogger { } } + private boolean fileIsUnder(String filePath, String directoryPath) { + if (directoryPath == null) { + return false; + } + + try { + return FileUtils.contains(new File(directoryPath).getCanonicalPath(), + new File(filePath).getCanonicalPath()); + } catch (IOException e) { + return false; + } + } + @VisibleForTesting PackageDynamicCode getPackageDynamicCodeInfo(String packageName) { return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName); } @VisibleForTesting - void writeDclEvent(int uid, String message) { - EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, uid, message); + void writeDclEvent(String subtag, int uid, String message) { + EventLog.writeEvent(SNET_TAG, subtag, uid, message); } - void record(int loaderUserId, String dexPath, - String owningPackageName, String loadingPackageName) { + void recordDex(int loaderUserId, String dexPath, String owningPackageName, + String loadingPackageName) { if (mPackageDynamicCodeLoading.record(owningPackageName, dexPath, - PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId, - loadingPackageName)) { + FILE_TYPE_DEX, loaderUserId, loadingPackageName)) { + mPackageDynamicCodeLoading.maybeWriteAsync(); + } + } + + /** + * Record that an app running in the specified uid has executed native code from the file at + * {@link path}. + */ + public void recordNative(int loadingUid, String path) { + String[] packages; + try { + packages = mPackageManager.getPackagesForUid(loadingUid); + if (packages == null || packages.length == 0) { + return; + } + } catch (RemoteException e) { + // Can't happen, we're local. + return; + } + + String loadingPackageName = packages[0]; + int loadingUserId = UserHandle.getUserId(loadingUid); + + if (mPackageDynamicCodeLoading.record(loadingPackageName, path, + FILE_TYPE_NATIVE, loadingUserId, loadingPackageName)) { mPackageDynamicCodeLoading.maybeWriteAsync(); } } diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index b54683673e7b..1a2b11559446 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -235,7 +235,7 @@ public class DexManager { continue; } - mDexLogger.record(loaderUserId, dexPath, searchResult.mOwningPackageName, + mDexLogger.recordDex(loaderUserId, dexPath, searchResult.mOwningPackageName, loadingAppInfo.packageName); if (classLoaderContexts != null) { diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java index 6d4bc8291611..cc26c9b5f76c 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java +++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java @@ -53,6 +53,9 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { // is represented in the text file format.) static final int FILE_TYPE_DEX = 'D'; + // Type code to indicate a secondary file containing native code. + static final int FILE_TYPE_NATIVE = 'N'; + private static final String TAG = "PackageDynamicCodeLoading"; private static final String FILE_VERSION_HEADER = "DCL1"; @@ -107,7 +110,7 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { */ boolean record(String owningPackageName, String filePath, int fileType, int ownerUserId, String loadingPackageName) { - if (fileType != FILE_TYPE_DEX) { + if (!isValidFileType(fileType)) { throw new IllegalArgumentException("Bad file type: " + fileType); } synchronized (mLock) { @@ -120,6 +123,10 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { } } + private static boolean isValidFileType(int fileType) { + return fileType == FILE_TYPE_DEX || fileType == FILE_TYPE_NATIVE; + } + /** * Return all packages that contain records of secondary dex files. (Note that data updates * asynchronously, so {@link #getPackageDynamicCodeInfo} may still return null if passed @@ -407,7 +414,7 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { if (packages.length == 0) { throw new IOException("Malformed line: " + line); } - if (type != FILE_TYPE_DEX) { + if (!isValidFileType(type)) { throw new IOException("Unknown file type: " + line); } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index d5af31300bf5..20d6d4e2ca79 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -1290,6 +1290,7 @@ public final class DefaultPermissionGrantPolicy { return mContext.getPackageManager().getPackageInfo(pkg, DEFAULT_PACKAGE_INFO_QUERY_FLAGS | extraFlags); } catch (NameNotFoundException e) { + Slog.e(TAG, "PackageNot found: " + pkg, e); return null; } } diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 9df0f72e2240..085b4afac0e2 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -43,10 +43,14 @@ 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; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; +import com.android.server.pm.Installer; +import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.PackageManagerServiceUtils; import java.io.File; @@ -56,12 +60,11 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; /** * Implementation of service that manages APK level rollbacks. @@ -103,9 +106,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { private final Context mContext; private final HandlerThread mHandlerThread; + private final Installer mInstaller; RollbackManagerServiceImpl(Context context) { mContext = context; + // Note that we're calling onStart here because this object is only constructed on + // SystemService#onStart. + mInstaller = new Installer(mContext); + mInstaller.onStart(); mHandlerThread = new HandlerThread("RollbackManagerServiceHandler"); mHandlerThread.start(); @@ -120,8 +128,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // expiration. getHandler().post(() -> ensureRollbackDataLoaded()); - PackageInstaller installer = mContext.getPackageManager().getPackageInstaller(); - installer.registerSessionCallback(new SessionCallback(), getHandler()); + PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller(); + packageInstaller.registerSessionCallback(new SessionCallback(), getHandler()); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_REPLACED); @@ -158,10 +166,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_TOKEN, -1); int installFlags = intent.getIntExtra( PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS, 0); + int[] installedUsers = intent.getIntArrayExtra( + PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS); File newPackageCodePath = new File(intent.getData().getPath()); getHandler().post(() -> { - boolean success = enableRollback(installFlags, newPackageCodePath); + boolean success = enableRollback(installFlags, newPackageCodePath, + installedUsers); int ret = PackageManagerInternal.ENABLE_ROLLBACK_SUCCEEDED; if (!success) { ret = PackageManagerInternal.ENABLE_ROLLBACK_FAILED; @@ -356,28 +367,30 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { parentSession.addChildSessionId(sessionId); } - final LocalIntentReceiver receiver = new LocalIntentReceiver(); - parentSession.commit(receiver.getIntentSender()); + 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; + } - Intent result = receiver.getResult(); - int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); - if (status != PackageInstaller.STATUS_SUCCESS) { - sendFailure(statusReceiver, "Rollback downgrade install failed: " - + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)); - return; - } + addRecentlyExecutedRollback(rollback); + sendSuccess(statusReceiver); - addRecentlyExecutedRollback(rollback); - sendSuccess(statusReceiver); + Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED, + Uri.fromParts("package", targetPackageName, + Manifest.permission.MANAGE_ROLLBACKS)); - Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED, - Uri.fromParts("package", targetPackageName, - Manifest.permission.MANAGE_ROLLBACKS)); + // TODO: This call emits the warning "Calling a method in the + // system process without a qualified user". Fix that. + mContext.sendBroadcast(broadcast); + } + ); - // TODO: This call emits the warning "Calling a method in the - // system process without a qualified user". Fix that. - mContext.sendBroadcast(broadcast); + parentSession.commit(receiver.getIntentSender()); } catch (IOException e) { Log.e(TAG, "Unable to roll back " + targetPackageName, e); sendFailure(statusReceiver, "IOException: " + e.toString()); @@ -620,12 +633,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { * staged for install with rollback enabled. Called before the package has * been installed. * - * @param id the id of the enable rollback request * @param installFlags information about what is being installed. * @param newPackageCodePath path to the package about to be installed. + * @param installedUsers the set of users for which a given package is installed. * @return true if enabling the rollback succeeds, false otherwise. */ - private boolean enableRollback(int installFlags, File newPackageCodePath) { + private boolean enableRollback(int installFlags, File newPackageCodePath, + int[] installedUsers) { if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { Log.e(TAG, "Rollbacks not supported for instant app install"); return false; @@ -690,6 +704,25 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { PackageRollbackInfo.PackageVersion installedVersion = new PackageRollbackInfo.PackageVersion(installedPackage.getLongVersionCode()); + for (int user : installedUsers) { + final int storageFlags; + if (StorageManager.isFileEncryptedNativeOrEmulated() + && !StorageManager.isUserKeyUnlocked(user)) { + // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy + // across app user data until the user unlocks their device. + Log.e(TAG, "User: " + user + " isn't unlocked, skipping CE userdata backup."); + storageFlags = Installer.FLAG_STORAGE_DE; + } else { + storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE; + } + + try { + mInstaller.snapshotAppData(packageName, user, storageFlags); + } catch (InstallerException ie) { + Log.e(TAG, "Unable to create app data snapshot for: " + packageName, ie); + } + } + PackageRollbackInfo info = new PackageRollbackInfo( packageName, newVersion, installedVersion); @@ -722,33 +755,64 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return true; } - // TODO: Don't copy this from PackageManagerShellCommand like this? - private static class LocalIntentReceiver { - private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>(); + @Override + public void restoreUserData(String packageName, int userId, int appId, long ceDataInode, + String seInfo, int token) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("restoureUserData may only be called by the system."); + } + + getHandler().post(() -> { + PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); + // 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) { + pmi.finishPackageInstall(token, false); + return; + } + + final int storageFlags; + if (StorageManager.isFileEncryptedNativeOrEmulated() + && !StorageManager.isUserKeyUnlocked(userId)) { + // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy + // across app user data until the user unlocks their device. + Log.e(TAG, "User: " + userId + " isn't unlocked, skipping CE userdata restore."); + + storageFlags = Installer.FLAG_STORAGE_DE; + } else { + storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE; + } + + try { + mInstaller.restoreAppDataSnapshot(packageName, appId, ceDataInode, + seInfo, userId, storageFlags); + } catch (InstallerException ie) { + Log.e(TAG, "Unable to restore app data snapshot: " + packageName, ie); + } + + pmi.finishPackageInstall(token, false); + }); + } + + private class LocalIntentReceiver { + final Consumer<Intent> mConsumer; + + LocalIntentReceiver(Consumer<Intent> consumer) { + mConsumer = consumer; + } private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { @Override public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { - try { - mResult.offer(intent, 5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + getHandler().post(() -> mConsumer.accept(intent)); } }; public IntentSender getIntentSender() { return new IntentSender((IIntentSender) mLocalSender); } - - public Intent getResult() { - try { - return mResult.take(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } } /** diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index f9a838fbf267..eb06bb24c24c 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -178,6 +178,8 @@ class RollbackStore { * rollback. */ void deleteAvailableRollback(RollbackData data) { + // TODO(narayan): Make sure we delete the userdata snapshot along with the backup of the + // actual app. removeFile(data.backupDir); } diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java new file mode 100644 index 000000000000..23c042a57ac8 --- /dev/null +++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.testharness; + +import android.annotation.Nullable; +import android.app.KeyguardManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.os.BatteryManager; +import android.os.Binder; +import android.os.IBinder; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.os.ShellCommand; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.util.Slog; + +import com.android.server.LocalServices; +import com.android.server.PersistentDataBlockManagerInternal; +import com.android.server.SystemService; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Set; + +/** + * Manages the Test Harness Mode service for setting up test harness mode on the device. + * + * <p>Test Harness Mode is a feature that allows the user to clean their device, retain ADB keys, + * and provision the device for Instrumentation testing. This means that all parts of the device + * that would otherwise interfere with testing (auto-syncing accounts, package verification, + * automatic updates, etc.) are all disabled by default but may be re-enabled by the user. + */ +public class TestHarnessModeService extends SystemService { + private static final String TAG = TestHarnessModeService.class.getSimpleName(); + private static final String TEST_HARNESS_MODE_PROPERTY = "persist.sys.test_harness"; + + private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal; + + public TestHarnessModeService(Context context) { + super(context); + } + + @Override + public void onStart() { + publishBinderService("testharness", mService); + } + + @Override + public void onBootPhase(int phase) { + switch (phase) { + case PHASE_SYSTEM_SERVICES_READY: + setUpTestHarnessMode(); + break; + case PHASE_BOOT_COMPLETED: + disableAutoSync(); + break; + } + super.onBootPhase(phase); + } + + private void setUpTestHarnessMode() { + Slog.d(TAG, "Setting up test harness mode"); + byte[] testHarnessModeData = getPersistentDataBlock().getTestHarnessModeData(); + if (testHarnessModeData == null || testHarnessModeData.length == 0) { + // There's no data to apply, so leave it as-is. + return; + } + PersistentData persistentData = PersistentData.fromBytes(testHarnessModeData); + + SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, persistentData.mEnabled ? "1" : "0"); + writeAdbKeysFile(persistentData); + // Clear out the data block so that we don't revert the ADB keys on every boot. + getPersistentDataBlock().clearTestHarnessModeData(); + + ContentResolver cr = getContext().getContentResolver(); + if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 0) { + // Enable ADB + Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 1); + } else { + // ADB is already enabled, we should restart the service so it picks up the new keys + android.os.SystemService.restart("adbd"); + } + + Settings.Global.putInt(cr, Settings.Global.PACKAGE_VERIFIER_ENABLE, 0); + Settings.Global.putInt( + cr, + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, + BatteryManager.BATTERY_PLUGGED_ANY); + Settings.Global.putInt(cr, Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE, 1); + Settings.Global.putInt(cr, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1); + + setDeviceProvisioned(); + } + + private void disableAutoSync() { + UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser(); + ContentResolver + .setMasterSyncAutomaticallyAsUser(false, primaryUser.getUserHandle().getIdentifier()); + } + + private void writeAdbKeysFile(PersistentData persistentData) { + Path adbKeys = Paths.get("/data/misc/adb/adb_keys"); + try { + OutputStream fileOutputStream = Files.newOutputStream(adbKeys); + fileOutputStream.write(persistentData.mAdbKeys); + fileOutputStream.close(); + + Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(adbKeys); + permissions.add(PosixFilePermission.GROUP_READ); + Files.setPosixFilePermissions(adbKeys, permissions); + } catch (IOException e) { + Slog.e(TAG, "Failed to set up adb keys", e); + // Note: if a device enters this block, it will remain UNAUTHORIZED in ADB, but all + // other settings will be set up. + } + } + + // Setting the device as provisioned skips the setup wizard. + private void setDeviceProvisioned() { + ContentResolver cr = getContext().getContentResolver(); + Settings.Global.putInt(cr, Settings.Global.DEVICE_PROVISIONED, 1); + Settings.Secure.putIntForUser( + cr, + Settings.Secure.USER_SETUP_COMPLETE, + 1, + UserHandle.USER_CURRENT); + } + + @Nullable + private PersistentDataBlockManagerInternal getPersistentDataBlock() { + if (mPersistentDataBlockManagerInternal == null) { + Slog.d(TAG, "Getting PersistentDataBlockManagerInternal from LocalServices"); + mPersistentDataBlockManagerInternal = + LocalServices.getService(PersistentDataBlockManagerInternal.class); + } + return mPersistentDataBlockManagerInternal; + } + + private final IBinder mService = new Binder() { + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback callback, ResultReceiver resultReceiver) { + (new TestHarnessModeShellCommand()) + .exec(this, in, out, err, args, callback, resultReceiver); + } + }; + + private class TestHarnessModeShellCommand extends ShellCommand { + @Override + public int onCommand(String cmd) { + switch (cmd) { + case "enable": + case "restore": + checkPermissions(); + final long originalId = Binder.clearCallingIdentity(); + try { + if (isDeviceSecure()) { + getErrPrintWriter().println( + "Test Harness Mode cannot be enabled if there is a lock " + + "screen"); + return 2; + } + return handleEnable(); + } finally { + Binder.restoreCallingIdentity(originalId); + } + default: + return handleDefaultCommands(cmd); + } + } + + private void checkPermissions() { + getContext().enforceCallingPermission( + android.Manifest.permission.ENABLE_TEST_HARNESS_MODE, + "You must hold android.permission.ENABLE_TEST_HARNESS_MODE " + + "to enable Test Harness Mode"); + } + + private boolean isDeviceSecure() { + UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser(); + KeyguardManager keyguardManager = getContext().getSystemService(KeyguardManager.class); + return keyguardManager.isDeviceSecure(primaryUser.id); + } + + private int handleEnable() { + Path adbKeys = Paths.get("/data/misc/adb/adb_keys"); + if (!Files.exists(adbKeys)) { + // This should only be accessible on eng builds that haven't yet set up ADB keys + getErrPrintWriter() + .println("No ADB keys stored; not enabling test harness mode"); + return 1; + } + + try (InputStream inputStream = Files.newInputStream(adbKeys)) { + long size = Files.size(adbKeys); + byte[] adbKeysBytes = new byte[(int) size]; + int numBytes = inputStream.read(adbKeysBytes); + if (numBytes != size) { + getErrPrintWriter().println("Failed to read all bytes of adb_keys"); + return 1; + } + PersistentData persistentData = new PersistentData(true, adbKeysBytes); + getPersistentDataBlock().setTestHarnessModeData(persistentData.toBytes()); + } catch (IOException e) { + Slog.e(TAG, "Failed to store ADB keys.", e); + getErrPrintWriter().println("Failed to enable Test Harness Mode"); + return 1; + } + + Intent i = new Intent(Intent.ACTION_FACTORY_RESET); + i.setPackage("android"); + i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + i.putExtra(Intent.EXTRA_REASON, TAG); + i.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true); + getContext().sendBroadcastAsUser(i, UserHandle.SYSTEM); + return 0; + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("About:"); + pw.println(" Test Harness Mode is a mode that the device can be placed in to prepare"); + pw.println(" the device for running UI tests. The device is placed into this mode by"); + pw.println(" first wiping all data from the device, preserving ADB keys."); + pw.println(); + pw.println(" By default, the following settings are configured:"); + pw.println(" * Package Verifier is disabled"); + pw.println(" * Stay Awake While Charging is enabled"); + pw.println(" * OTA Updates are disabled"); + pw.println(" * Auto-Sync for accounts is disabled"); + pw.println(); + pw.println(" Other apps may configure themselves differently in Test Harness Mode by"); + pw.println(" checking ActivityManager.isRunningInUserTestHarness()"); + pw.println(); + pw.println("Test Harness Mode commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(); + pw.println(" enable|restore"); + pw.println(" Erase all data from this device and enable Test Harness Mode,"); + pw.println(" preserving the stored ADB keys currently on the device and toggling"); + pw.println(" settings in a way that are conducive to Instrumentation testing."); + } + } + + /** + * The object that will serialize/deserialize the Test Harness Mode data to and from the + * persistent data block. + */ + public static class PersistentData { + static final byte VERSION_1 = 1; + + final int mVersion; + final boolean mEnabled; + final byte[] mAdbKeys; + + PersistentData(boolean enabled, byte[] adbKeys) { + this(VERSION_1, enabled, adbKeys); + } + + PersistentData(int version, boolean enabled, byte[] adbKeys) { + this.mVersion = version; + this.mEnabled = enabled; + this.mAdbKeys = adbKeys; + } + + static PersistentData fromBytes(byte[] bytes) { + try { + DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes)); + int version = is.readInt(); + boolean enabled = is.readBoolean(); + int adbKeysLength = is.readInt(); + byte[] adbKeys = new byte[adbKeysLength]; + is.readFully(adbKeys); + return new PersistentData(version, enabled, adbKeys); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + byte[] toBytes() { + try { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(os); + dos.writeInt(VERSION_1); + dos.writeBoolean(mEnabled); + dos.writeInt(mAdbKeys.length); + dos.write(mAdbKeys); + dos.close(); + return os.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index b3eafa4d25ce..45689ce73c9f 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -65,7 +65,6 @@ import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.SystemService; -import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.utils.ManagedApplicationService; import com.android.server.utils.ManagedApplicationService.BinderChecker; import com.android.server.utils.ManagedApplicationService.LogEvent; @@ -623,14 +622,6 @@ public class VrManagerService extends SystemService } @Override - public void setVrInputMethod(ComponentName componentName) { - enforceCallerPermissionAnyOf(Manifest.permission.RESTRICTED_VR_ACCESS); - InputMethodManagerInternal imm = - LocalServices.getService(InputMethodManagerInternal.class); - imm.startVrInputMethodNoCheck(componentName); - } - - @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java index e817dd47e756..65d66f44b5dd 100644 --- a/services/core/java/com/android/server/wm/ActivityDisplay.java +++ b/services/core/java/com/android/server/wm/ActivityDisplay.java @@ -558,26 +558,22 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> } /** - * Pause all activities in either all of the stacks or just the back stacks. This is done before - * resuming a new activity and to make sure that previously active activities are - * paused in stacks that are no longer visible or in pinned windowing mode. This does not - * pause activities in visible stacks, so if an activity is launched within the same stack/task, - * then we should explicitly pause that stack's top activity. + * Pause all activities in either all of the stacks or just the back stacks. * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving(). * @param resuming The resuming activity. * @param dontWait The resuming activity isn't going to wait for all activities to be paused * before resuming. - * @return {@code true} if any activity was paused as a result of this call. + * @return true if any activity was paused as a result of this call. */ boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) { boolean someActivityPaused = false; for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = mStacks.get(stackNdx); - final ActivityRecord resumedActivity = stack.getResumedActivity(); - if (resumedActivity != null - && (!stack.shouldBeVisible(resuming) || !stack.isFocusable())) { + // TODO(b/111541062): Check if resumed activity on this display instead + if (!mRootActivityContainer.isTopDisplayFocusedStack(stack) + && stack.getResumedActivity() != null) { if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack + - " mResumedActivity=" + resumedActivity); + " mResumedActivity=" + stack.getResumedActivity()); someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming, dontWait); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 6213fa02cb9f..b8634d88319a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1946,84 +1946,30 @@ final class ActivityRecord extends ConfigurationContainer { try { mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, WindowVisibilityItem.obtain(true /* showWindow */)); - makeActiveIfNeeded(null /* activeActivity*/); - } catch (Exception e) { - Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e); - } - } - - /** - * Make activity resumed or paused if needed. - * @param activeActivity an activity that is resumed or just completed pause action. - * We won't change the state of this activity. - */ - boolean makeActiveIfNeeded(ActivityRecord activeActivity) { - if (shouldResumeActivity(activeActivity)) { - if (DEBUG_VISIBILITY) { - Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this); - } - return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */, - null /* options */); - } else if (shouldPauseActivity(activeActivity)) { - if (DEBUG_VISIBILITY) { - Slog.v("TAG_VISIBILITY", "Pause visible activity, " + this); - } - // An activity must be in the {@link PAUSING} state for the system to validate - // the move to {@link PAUSED}. - setState(PAUSING, "makeVisibleIfNeeded"); - try { + if (shouldPauseWhenBecomingVisible()) { + // An activity must be in the {@link PAUSING} state for the system to validate + // the move to {@link PAUSED}. + setState(PAUSING, "makeVisibleIfNeeded"); mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, PauseActivityItem.obtain(finishing, false /* userLeaving */, configChangeFlags, false /* dontReport */)); - } catch (Exception e) { - Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e); } + } catch (Exception e) { + Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e); } - return false; } - /** - * Check if activity should be moved to PAUSED state. The activity: - * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)}) - * - should be non-focusable - * - should not be currently pausing or paused - * @param activeActivity the activity that is active or just completed pause action. We won't - * resume if this activity is active. - */ - private boolean shouldPauseActivity(ActivityRecord activeActivity) { - return shouldMakeActive(activeActivity) && !isFocusable() && !isState(PAUSING, PAUSED); - } - - /** - * Check if activity should be moved to RESUMED state. The activity: - * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)}) - * - should be focusable - * @param activeActivity the activity that is active or just completed pause action. We won't - * resume if this activity is active. - */ - private boolean shouldResumeActivity(ActivityRecord activeActivity) { - return shouldMakeActive(activeActivity) && isFocusable() && !isState(RESUMED); - } - - /** - * Check if activity is eligible to be made active (resumed of paused). The activity: - * - should be paused, stopped or stopping - * - should not be the currently active one - * - should be either the topmost in task, or right below the top activity that is finishing - * If all of these conditions are not met at the same time, the activity cannot be made active. - */ - private boolean shouldMakeActive(ActivityRecord activeActivity) { - // If the activity is stopped, stopping, cycle to an active state. We avoid doing + /** Check if activity should be moved to PAUSED state when it becomes visible. */ + private boolean shouldPauseWhenBecomingVisible() { + // If the activity is stopped or stopping, cycle to the paused state. We avoid doing // this when there is an activity waiting to become translucent as the extra binder // calls will lead to noticeable jank. A later call to - // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to a proper - // active state. - if (!isState(RESUMED, PAUSED, STOPPED, STOPPING) - || getActivityStack().mTranslucentActivityWaiting != null) { - return false; - } - - if (this == activeActivity) { + // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to the proper + // paused state. We also avoid doing this for the activity the stack supervisor + // considers the resumed activity, as normal means will bring the activity from STOPPED + // to RESUMED. Adding PAUSING in this scenario will lead to double lifecycles. + if (!isState(STOPPED, STOPPING) || getActivityStack().mTranslucentActivityWaiting != null + || isResumedActivityOnDisplay()) { return false; } @@ -2033,14 +1979,14 @@ final class ActivityRecord extends ConfigurationContainer { throw new IllegalStateException("Activity not found in its task"); } if (positionInTask == task.mActivities.size() - 1) { - // It's the topmost activity in the task - should become resumed now + // It's the topmost activity in the task - should become paused now return true; } // Check if activity above is finishing now and this one becomes the topmost in task. final ActivityRecord activityAbove = task.mActivities.get(positionInTask + 1); if (activityAbove.finishing && results == null) { - // We will only allow making active if activity above wasn't launched for result. - // Otherwise it will cause this activity to resume before getting result. + // We will only allow pausing if activity above wasn't launched for result. Otherwise it + // will cause this activity to resume before getting result. return true; } return false; diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 3aef8e1f84bf..891c3da90b93 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -357,11 +357,6 @@ class ActivityStack extends ConfigurationContainer { */ boolean mForceHidden = false; - /** - * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively - */ - boolean mInResumeTopActivity = false; - private boolean mUpdateBoundsDeferred; private boolean mUpdateBoundsDeferredCalled; private boolean mUpdateDisplayedBoundsDeferredCalled; @@ -1737,7 +1732,6 @@ class ActivityStack extends ConfigurationContainer { "Activity paused: token=" + token + ", timeout=" + timeout); final ActivityRecord r = isInStackLocked(token); - if (r != null) { mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); if (mPausingActivity == r) { @@ -2094,7 +2088,8 @@ class ActivityStack extends ConfigurationContainer { boolean aboveTop = top != null; final boolean stackShouldBeVisible = shouldBeVisible(starting); boolean behindFullscreenActivity = !stackShouldBeVisible; - boolean resumeNextActivity = isFocusable() && isInStackLocked(starting) == null; + boolean resumeNextActivity = mRootActivityContainer.isTopDisplayFocusedStack(this) + && (isInStackLocked(starting) == null); final boolean isTopNotPinnedStack = isAttached() && getDisplay().isTopNotPinnedStack(this); for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { @@ -2155,10 +2150,6 @@ class ActivityStack extends ConfigurationContainer { if (r.handleAlreadyVisible()) { resumeNextActivity = false; } - - if (notifyClients) { - r.makeActiveIfNeeded(starting); - } } else { r.makeVisibleIfNeeded(starting, notifyClients); } @@ -2336,7 +2327,7 @@ class ActivityStack extends ConfigurationContainer { r.setVisible(true); } if (r != starting) { - mStackSupervisor.startSpecificActivityLocked(r, andResume, true /* checkConfig */); + mStackSupervisor.startSpecificActivityLocked(r, andResume, false); return true; } } @@ -2514,7 +2505,7 @@ class ActivityStack extends ConfigurationContainer { */ @GuardedBy("mService") boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) { - if (mInResumeTopActivity) { + if (mStackSupervisor.inResumeTopActivity) { // Don't even start recursing. return false; } @@ -2522,7 +2513,7 @@ class ActivityStack extends ConfigurationContainer { boolean result = false; try { // Protect against recursion. - mInResumeTopActivity = true; + mStackSupervisor.inResumeTopActivity = true; result = resumeTopActivityInnerLocked(prev, options); // When resuming the top activity, it may be necessary to pause the top activity (for @@ -2537,7 +2528,7 @@ class ActivityStack extends ConfigurationContainer { checkReadyForSleep(); } } finally { - mInResumeTopActivity = false; + mStackSupervisor.inResumeTopActivity = false; } return result; @@ -2570,7 +2561,7 @@ class ActivityStack extends ConfigurationContainer { // Find the next top-most activity to resume in this stack that is not finishing and is // focusable. If it is not focusable, we will fall into the case below to resume the // top activity in the next focusable task. - ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */); + final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */); final boolean hasRunningActivity = next != null; @@ -2658,12 +2649,6 @@ class ActivityStack extends ConfigurationContainer { if (!mRootActivityContainer.allPausedActivitiesComplete()) { if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE, "resumeTopActivityLocked: Skip resume: some activity pausing."); - - // Adding previous activity to the waiting visible list, or it would be stopped - // before top activity being visible. - if (prev != null && !next.nowVisible) { - mStackSupervisor.mActivitiesWaitingForVisibleActivity.add(prev); - } return false; } @@ -2873,9 +2858,7 @@ class ActivityStack extends ConfigurationContainer { // the screen based on the new activity order. boolean notUpdated = true; - // Activity should also be visible if set mLaunchTaskBehind to true (see - // ActivityRecord#shouldBeVisibleIgnoringKeyguard()). - if (shouldBeVisible(next)) { + if (isFocusedStackOnDisplay()) { // We have special rotation behavior when here is some active activity that // requests specific orientation or Keyguard is locked. Make sure all activity // visibilities are set correctly as well as the transition is updated if needed @@ -4104,12 +4087,6 @@ class ActivityStack extends ConfigurationContainer { mStackSupervisor.mFinishingActivities.add(r); r.resumeKeyDispatchingLocked(); mRootActivityContainer.resumeFocusedStacksTopActivities(); - // If activity was not paused at this point - explicitly pause it to start finishing - // process. Finishing will be completed once it reports pause back. - if (r.isState(RESUMED) && mPausingActivity != null) { - startPausingLocked(false /* userLeaving */, false /* uiSleeping */, next /* resuming */, - false /* dontWait */); - } return r; } diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index a83ef34f1cac..3a288ca5560d 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -327,6 +327,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { */ PowerManager.WakeLock mGoingToSleep; + /** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */ + boolean inResumeTopActivity; + /** * Temporary rect used during docked stack resize calculation so we don't need to create a new * object each time. diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 43c12064a3c1..280709461c98 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -179,10 +179,7 @@ public class ActivityStartController { .setActivityOptions(options.toBundle()) .execute(); mLastHomeActivityStartRecord = tmpOutRecord[0]; - final ActivityDisplay display = - mService.mRootActivityContainer.getActivityDisplay(displayId); - final ActivityStack homeStack = display != null ? display.getHomeStack() : null; - if (homeStack != null && homeStack.mInResumeTopActivity) { + if (mSupervisor.inResumeTopActivity) { // If we are in resume section already, home activity will be initialized, but not // resumed (to avoid recursive resume) and will stay that way until something pokes it // again. We need to schedule another resume. diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 4e2dffc2ba78..d36e545aa74f 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1629,7 +1629,7 @@ class ActivityStarter { // Also, we don't want to resume activities in a task that currently has an overlay // as the starting activity just needs to be in the visible paused state until the // over is removed. - mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS); + mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); // Go ahead and tell window manager to execute app transition for this activity // since the app transition will not be triggered through the resume channel. mTargetStack.getDisplay().mDisplayContent.executeAppTransition(); diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 9361e7fc3116..750c5ca5922e 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -2404,7 +2404,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree @Override protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) { if (!mSurfaceAnimator.hasLeash()) { - t.reparent(mSurfaceControl, newParent.getHandle()); + t.reparent(mSurfaceControl, newParent); } } @@ -2450,7 +2450,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree t.setWindowCrop(mAnimationBoundsLayer, mTmpRect); // Reparent leash to animation bounds layer. - t.reparent(leash, mAnimationBoundsLayer.getHandle()); + t.reparent(leash, mAnimationBoundsLayer); } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index ac1159a3272a..8fefd352e027 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4588,7 +4588,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * Reparents the given surface to mOverlayLayer. */ void reparentToOverlay(Transaction transaction, SurfaceControl surface) { - transaction.reparent(surface, mOverlayLayer.getHandle()); + transaction.reparent(surface, mOverlayLayer); } void applyMagnificationSpec(MagnificationSpec spec) { @@ -4831,11 +4831,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * Re-parent the DisplayContent's top surfaces, {@link #mWindowingLayer} and * {@link #mOverlayLayer} to the specified surfaceControl. * - * @param surfaceControlHandle The handle for the new SurfaceControl, where the DisplayContent's + * @param surfaceControlHandle The new SurfaceControl, where the DisplayContent's * surfaces will be re-parented to. */ - void reparentDisplayContent(IBinder surfaceControlHandle) { - mPendingTransaction.reparent(mWindowingLayer, surfaceControlHandle) - .reparent(mOverlayLayer, surfaceControlHandle); + void reparentDisplayContent(SurfaceControl sc) { + mPendingTransaction.reparent(mWindowingLayer, sc) + .reparent(mOverlayLayer, sc); } } diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java index c4a853dc3483..9b7214120aed 100644 --- a/services/core/java/com/android/server/wm/RootActivityContainer.java +++ b/services/core/java/com/android/server/wm/RootActivityContainer.java @@ -1108,41 +1108,28 @@ class RootActivityContainer extends ConfigurationContainer return false; } - boolean result = false; if (targetStack != null && (targetStack.isTopStackOnDisplay() || getTopDisplayFocusedStack() == targetStack)) { - result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions); + return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions); } + // Resume all top activities in focused stacks on all displays. for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - boolean resumedOnDisplay = false; final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); - final ActivityRecord topRunningActivity = stack.topRunningActivityLocked(); - if (!stack.isFocusableAndVisible() || topRunningActivity == null) { - continue; - } - if (topRunningActivity.isState(RESUMED)) { - // Kick off any lingering app transitions form the MoveTaskToFront operation. - stack.executeAppTransition(targetOptions); - } else { - resumedOnDisplay |= topRunningActivity.makeActiveIfNeeded(target); - } + final ActivityStack focusedStack = display.getFocusedStack(); + if (focusedStack == null) { + continue; } - if (!resumedOnDisplay) { - // In cases when there are no valid activities (e.g. device just booted or launcher - // crashed) it's possible that nothing was resumed on a display. Requesting resume - // of top activity in focused stack explicitly will make sure that at least home - // activity is started and resumed, and no recursion occurs. - final ActivityStack focusedStack = display.getFocusedStack(); - if (focusedStack != null) { - focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions); - } + final ActivityRecord r = focusedStack.topRunningActivityLocked(); + if (r == null || !r.isState(RESUMED)) { + focusedStack.resumeTopActivityUncheckedLocked(null, null); + } else if (r.isState(RESUMED)) { + // Kick off any lingering app transitions form the MoveTaskToFront operation. + focusedStack.executeAppTransition(targetOptions); } } - return result; + return false; } void applySleepTokens(boolean applyToStacks) { diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 9d9b48a5b36a..1a8a9110c649 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -199,7 +199,7 @@ class SurfaceAnimator { * @see #setLayer */ void reparent(Transaction t, SurfaceControl newParent) { - t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent.getHandle()); + t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent); } /** @@ -228,8 +228,8 @@ class SurfaceAnimator { // Cancel source animation, but don't let animation runner cancel the animation. from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */); - t.reparent(surface, mLeash.getHandle()); - t.reparent(mLeash, parent.getHandle()); + t.reparent(surface, mLeash); + t.reparent(mLeash, parent); mAnimatable.onAnimationLeashCreated(t, mLeash); mService.mAnimationTransferMap.put(mAnimation, this); } @@ -275,7 +275,7 @@ class SurfaceAnimator { final boolean destroy = mLeash != null && surface != null && parent != null; if (destroy) { if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent"); - t.reparent(surface, parent.getHandle()); + t.reparent(surface, parent); scheduleAnim = true; } mService.mAnimationTransferMap.remove(mAnimation); @@ -308,7 +308,7 @@ class SurfaceAnimator { if (!hidden) { t.show(leash); } - t.reparent(surface, leash.getHandle()); + t.reparent(surface, leash); return leash; } diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 0529ed128130..69dcaf473b12 100644 --- a/services/core/java/com/android/server/wm/TaskRecord.java +++ b/services/core/java/com/android/server/wm/TaskRecord.java @@ -698,14 +698,6 @@ class TaskRecord extends ConfigurationContainer { return false; } - final boolean toTopOfStack = position == MAX_VALUE; - if (toTopOfStack && toStack.getResumedActivity() != null - && toStack.topRunningActivityLocked() != null) { - // Pause the resumed activity on the target stack while re-parenting task on top of it. - toStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, - null /* resuming */, false /* pauseImmediately */); - } - final int toStackWindowingMode = toStack.getWindowingMode(); final ActivityRecord topActivity = getTopActivity(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c8c834f4f18e..c6679a9ad0d7 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7334,7 +7334,7 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void reparentDisplayContent(int displayId, IBinder surfaceControlHandle) { + public void reparentDisplayContent(int displayId, SurfaceControl sc) { final Display display = mDisplayManager.getDisplay(displayId); if (display == null) { throw new IllegalArgumentException( @@ -7351,7 +7351,7 @@ public class WindowManagerService extends IWindowManager.Stub long token = Binder.clearCallingIdentity(); try { DisplayContent displayContent = getDisplayContentOrCreate(displayId, null); - displayContent.reparentDisplayContent(surfaceControlHandle); + displayContent.reparentDisplayContent(sc); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 0977323754f9..fb7e47d70d64 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -4223,7 +4223,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setPasswordHistoryLength(ComponentName who, int length, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } Preconditions.checkNotNull(who, "ComponentName is null"); @@ -4246,13 +4246,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public int getPasswordHistoryLength(ComponentName who, int userHandle, boolean parent) { + if (!mLockPatternUtils.hasSecureLockScreen()) { + return 0; + } return getStrictestPasswordRequirement(who, userHandle, parent, admin -> admin.passwordHistoryLength, PASSWORD_QUALITY_UNSPECIFIED); } @Override public void setPasswordExpirationTimeout(ComponentName who, long timeout, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } Preconditions.checkNotNull(who, "ComponentName is null"); @@ -4288,7 +4291,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ @Override public long getPasswordExpirationTimeout(ComponentName who, int userHandle, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return 0L; } enforceFullCrossUsersPermission(userHandle); @@ -4423,7 +4426,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public long getPasswordExpiration(ComponentName who, int userHandle, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return 0L; } enforceFullCrossUsersPermission(userHandle); @@ -4770,6 +4773,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public int getCurrentFailedPasswordAttempts(int userHandle, boolean parent) { + if (!mLockPatternUtils.hasSecureLockScreen()) { + return 0; + } enforceFullCrossUsersPermission(userHandle); synchronized (getLockObject()) { if (!isCallerWithSystemUid()) { @@ -4789,7 +4795,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setMaximumFailedPasswordsForWipe(ComponentName who, int num, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } Preconditions.checkNotNull(who, "ComponentName is null"); @@ -4815,7 +4821,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public int getMaximumFailedPasswordsForWipe(ComponentName who, int userHandle, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return 0; } enforceFullCrossUsersPermission(userHandle); @@ -4829,7 +4835,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return UserHandle.USER_NULL; } enforceFullCrossUsersPermission(userHandle); @@ -4910,6 +4916,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException { + if (!mLockPatternUtils.hasSecureLockScreen()) { + Slog.w(LOG_TAG, "Cannot reset password when the device has no lock screen"); + return false; + } + final int callingUid = mInjector.binderGetCallingUid(); final int userHandle = mInjector.userHandleGetCallingUserId(); @@ -5252,7 +5263,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setRequiredStrongAuthTimeout(ComponentName who, long timeoutMs, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } Preconditions.checkNotNull(who, "ComponentName is null"); @@ -5285,7 +5296,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ @Override public long getRequiredStrongAuthTimeout(ComponentName who, int userId, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS; } enforceFullCrossUsersPermission(userId); @@ -6494,7 +6505,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ @Override public void setActivePasswordState(PasswordMetrics metrics, int userHandle) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } enforceFullCrossUsersPermission(userHandle); @@ -6514,7 +6525,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void reportPasswordChanged(@UserIdInt int userId) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } enforceFullCrossUsersPermission(userId); @@ -8800,7 +8811,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setTrustAgentConfiguration(ComponentName admin, ComponentName agent, PersistableBundle args, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } Preconditions.checkNotNull(admin, "admin is null"); @@ -8817,7 +8828,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public List<PersistableBundle> getTrustAgentConfiguration(ComponentName admin, ComponentName agent, int userHandle, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return null; } Preconditions.checkNotNull(agent, "agent null"); @@ -13215,7 +13226,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean setResetPasswordToken(ComponentName admin, byte[] token) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return false; } if (token == null || token.length < 32) { @@ -13243,7 +13254,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean clearResetPasswordToken(ComponentName admin) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return false; } synchronized (getLockObject()) { @@ -13269,6 +13280,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean isResetPasswordTokenActive(ComponentName admin) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { + return false; + } synchronized (getLockObject()) { final int userHandle = mInjector.userHandleGetCallingUserId(); getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); @@ -13290,6 +13304,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean resetPasswordWithToken(ComponentName admin, String passwordOrNull, byte[] token, int flags) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { + return false; + } Preconditions.checkNotNull(token); synchronized (getLockObject()) { final int userHandle = mInjector.userHandleGetCallingUserId(); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 98385c93aafe..623990ba211a 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -136,6 +136,7 @@ import com.android.server.stats.StatsCompanionService; import com.android.server.statusbar.StatusBarManagerService; import com.android.server.storage.DeviceStorageMonitorService; import com.android.server.telecom.TelecomLoaderService; +import com.android.server.testharness.TestHarnessModeService; import com.android.server.textclassifier.TextClassificationManagerService; import com.android.server.textservices.TextServicesManagerService; import com.android.server.trust.TrustManagerService; @@ -1157,6 +1158,10 @@ public final class SystemServer { traceBeginAndSlog("StartPersistentDataBlock"); mSystemServiceManager.startService(PersistentDataBlockService.class); traceEnd(); + + traceBeginAndSlog("StartTestHarnessMode"); + mSystemServiceManager.startService(TestHarnessModeService.class); + traceEnd(); } if (hasPdb || OemLockService.isHalPresent()) { diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index 1a16e568ca53..53d72bb9a415 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -1376,6 +1376,45 @@ public class DeviceIdleControllerTest { verifyLightStateConditions(LIGHT_STATE_ACTIVE); } + @Test + public void testStepToIdleMode() { + float delta = mDeviceIdleController.MIN_PRE_IDLE_FACTOR_CHANGE; + for (int mode = PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL; + mode <= PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG; + mode++) { + int ret = mDeviceIdleController.setPreIdleTimeoutMode(mode); + if (mode == PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL) { + assertEquals("setPreIdleTimeoutMode: " + mode + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret); + } else { + assertEquals("setPreIdleTimeoutMode: " + mode + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret); + } + //TODO(b/123045185): Mocked Handler of DeviceIdleController to make message loop + //workable in this test class + mDeviceIdleController.updatePreIdleFactor(); + float expectedfactor = mDeviceIdleController.getPreIdleTimeoutByMode(mode); + float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor(); + assertEquals("Pre idle time factor of mode [" + mode + "].", + expectedfactor, curfactor, delta); + mDeviceIdleController.resetPreIdleTimeoutMode(); + mDeviceIdleController.updatePreIdleFactor(); + + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_INACTIVE); + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_PENDING); + + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_SENSING); + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_LOCATING); + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_QUICK_DOZE_DELAY); + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_MAINTENANCE); + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE); + checkMaybeDoAnImmediateMaintenance(expectedfactor); + } + float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor(); + assertEquals("Pre idle time factor of mode default.", + 1.0f, curfactor, delta); + } + private void enterDeepState(int state) { switch (state) { case STATE_ACTIVE: @@ -1599,4 +1638,84 @@ public class DeviceIdleControllerTest { fail("Conditions for " + lightStateToString(expectedLightState) + " unknown."); } } + + private void checkNextAlarmTimeWithNewPreIdleFactor(float factor, int state) { + final long errorTolerance = 1000; + enterDeepState(state); + long now = SystemClock.elapsedRealtime(); + long alarm = mDeviceIdleController.getNextAlarmTime(); + if (state == STATE_INACTIVE || state == STATE_IDLE_PENDING) { + int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor); + if (Float.compare(factor, 1.0f) == 0) { + assertEquals("setPreIdleTimeoutMode: " + factor + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret); + } else { + assertEquals("setPreIdleTimeoutMode: " + factor + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret); + } + if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) { + mDeviceIdleController.updatePreIdleFactor(); + long newAlarm = mDeviceIdleController.getNextAlarmTime(); + long newDelay = (long) ((alarm - now) * factor); + assertTrue("setPreIdleTimeoutFactor: " + factor, + Math.abs(newDelay - (newAlarm - now)) < errorTolerance); + mDeviceIdleController.resetPreIdleTimeoutMode(); + mDeviceIdleController.updatePreIdleFactor(); + mDeviceIdleController.maybeDoImmediateMaintenance(); + newAlarm = mDeviceIdleController.getNextAlarmTime(); + assertTrue("resetPreIdleTimeoutMode from: " + factor, + Math.abs(newAlarm - alarm) < errorTolerance); + mDeviceIdleController.setPreIdleTimeoutFactor(factor); + now = SystemClock.elapsedRealtime(); + enterDeepState(state); + newAlarm = mDeviceIdleController.getNextAlarmTime(); + assertTrue("setPreIdleTimeoutFactor: " + factor + " before step to idle", + Math.abs(newDelay - (newAlarm - now)) < errorTolerance); + mDeviceIdleController.resetPreIdleTimeoutMode(); + mDeviceIdleController.updatePreIdleFactor(); + mDeviceIdleController.maybeDoImmediateMaintenance(); + } + } else { + mDeviceIdleController.setPreIdleTimeoutFactor(factor); + mDeviceIdleController.updatePreIdleFactor(); + long newAlarm = mDeviceIdleController.getNextAlarmTime(); + assertTrue("setPreIdleTimeoutFactor: " + factor + + " shounld not change next alarm" , + (newAlarm == alarm)); + mDeviceIdleController.resetPreIdleTimeoutMode(); + mDeviceIdleController.updatePreIdleFactor(); + mDeviceIdleController.maybeDoImmediateMaintenance(); + } + } + + private void checkMaybeDoAnImmediateMaintenance(float factor) { + int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor); + final long minuteInMillis = 60 * 1000; + if (Float.compare(factor, 1.0f) == 0) { + assertEquals("setPreIdleTimeoutMode: " + factor + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret); + } else { + assertEquals("setPreIdleTimeoutMode: " + factor + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret); + } + if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) { + enterDeepState(STATE_IDLE); + long now = SystemClock.elapsedRealtime(); + long alarm = mDeviceIdleController.getNextAlarmTime(); + mDeviceIdleController.setIdleStartTimeForTest( + now - (long) (mConstants.IDLE_TIMEOUT * 0.6)); + mDeviceIdleController.maybeDoImmediateMaintenance(); + long newAlarm = mDeviceIdleController.getNextAlarmTime(); + assertTrue("maintenance not reschedule IDLE_TIMEOUT * 0.6", + newAlarm == alarm); + mDeviceIdleController.setIdleStartTimeForTest( + now - (long) (mConstants.IDLE_TIMEOUT * 1.2)); + mDeviceIdleController.maybeDoImmediateMaintenance(); + newAlarm = mDeviceIdleController.getNextAlarmTime(); + assertTrue("maintenance not reschedule IDLE_TIMEOUT * 1.2", + (newAlarm - now) < minuteInMillis); + mDeviceIdleController.resetPreIdleTimeoutMode(); + mDeviceIdleController.updatePreIdleFactor(); + } + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index 57ee6dcad9f2..cad71a26a76b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -25,6 +25,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; +import static com.android.server.job.JobSchedulerService.NEVER_INDEX; import static com.android.server.job.JobSchedulerService.RARE_INDEX; import static com.android.server.job.JobSchedulerService.WORKING_INDEX; @@ -370,16 +371,19 @@ public class QuotaControllerTest { mQuotaController.saveTimingSession(0, "com.android.test.stay", one); ExecutionStats expectedStats = new ExecutionStats(); - expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; - mQuotaController.onAppRemovedLocked("com.android.test.remove", 10001); + final int uid = 10001; + mQuotaController.onAppRemovedLocked("com.android.test.remove", uid); assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove")); assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay")); assertEquals(expectedStats, mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX)); assertNotEquals(expectedStats, mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX)); + + assertFalse(mQuotaController.getForegroundUids().get(uid)); } @Test @@ -405,7 +409,7 @@ public class QuotaControllerTest { mQuotaController.saveTimingSession(10, "com.android.test", one); ExecutionStats expectedStats = new ExecutionStats(); - expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; mQuotaController.onUserRemovedLocked(0); @@ -440,14 +444,14 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS; // Invalid time is now +24 hours since there are no sessions at all for the app. - expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats); assertEquals(expectedStats, inputStats); inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS; // Invalid time is now +18 hours since there are no sessions in the window but the earliest // session is 6 hours ago. - expectedStats.invalidTimeElapsed = now + 18 * HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 18 * HOUR_IN_MILLIS; expectedStats.executionTimeInWindowMs = 0; expectedStats.bgJobCountInWindow = 0; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -457,7 +461,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS; // Invalid time is now since the session straddles the window cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 3; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -468,7 +472,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS; // Invalid time is now since the start of the session is at the very edge of the window // cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 3; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -479,7 +483,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS; // Invalid time is now +44 minutes since the earliest session in the window is now-5 // minutes. - expectedStats.invalidTimeElapsed = now + 44 * MINUTE_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS; expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 3; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -489,7 +493,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS; // Invalid time is now since the session is at the very edge of the window cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 4; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -500,7 +504,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS; // Invalid time is now since the start of the session is at the very edge of the window // cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 5; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -510,7 +514,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; // Invalid time is now since the session straddles the window cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 10; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -523,7 +527,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS; // Invalid time is now +59 minutes since the earliest session in the window is now-121 // minutes. - expectedStats.invalidTimeElapsed = now + 59 * MINUTE_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 59 * MINUTE_IN_MILLIS; expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 10; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -536,7 +540,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS; // Invalid time is now since the start of the session is at the very edge of the window // cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 15; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -546,14 +550,14 @@ public class QuotaControllerTest { mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); assertEquals(expectedStats, inputStats); - // Make sure invalidTimeElapsed is set correctly when it's dependent on the max period. + // Make sure expirationTimeElapsed is set correctly when it's dependent on the max period. mQuotaController.getTimingSessions(0, "com.android.test") .add(0, createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3)); inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; // Invalid time is now +1 hour since the earliest session in the max period is 1 hour // before the end of the max period cutoff time. - expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 15; expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS; @@ -569,7 +573,7 @@ public class QuotaControllerTest { 2 * MINUTE_IN_MILLIS, 2)); inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; // Invalid time is now since the earlist session straddles the max period cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 15; expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS; @@ -599,7 +603,7 @@ public class QuotaControllerTest { // Active expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS; - expectedStats.invalidTimeElapsed = now + 4 * MINUTE_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS; expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 5; expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; @@ -609,7 +613,7 @@ public class QuotaControllerTest { // Working expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 10; expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; @@ -621,7 +625,7 @@ public class QuotaControllerTest { // Frequent expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; - expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 15; expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; @@ -633,7 +637,7 @@ public class QuotaControllerTest { // Rare expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; - expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 20; expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; @@ -675,7 +679,7 @@ public class QuotaControllerTest { ExecutionStats expectedStats = new ExecutionStats(); expectedStats.windowSizeMs = originalStatsActive.windowSizeMs; - expectedStats.invalidTimeElapsed = originalStatsActive.invalidTimeElapsed; + expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed; expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs; expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow; expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs; @@ -688,7 +692,7 @@ public class QuotaControllerTest { assertEquals(expectedStats, newStatsActive); expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs; - expectedStats.invalidTimeElapsed = originalStatsWorking.invalidTimeElapsed; + expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed; expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs; expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow; expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed; @@ -698,7 +702,7 @@ public class QuotaControllerTest { assertNotEquals(expectedStats, newStatsWorking); expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs; - expectedStats.invalidTimeElapsed = originalStatsFrequent.invalidTimeElapsed; + expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed; expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs; expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow; expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed; @@ -708,7 +712,7 @@ public class QuotaControllerTest { assertNotEquals(expectedStats, newStatsFrequent); expectedStats.windowSizeMs = originalStatsRare.windowSizeMs; - expectedStats.invalidTimeElapsed = originalStatsRare.invalidTimeElapsed; + expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed; expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs; expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow; expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed; @@ -719,6 +723,77 @@ public class QuotaControllerTest { } @Test + public void testIsWithinQuotaLocked_NeverApp() { + assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX)); + } + + @Test + public void testIsWithinQuotaLocked_Charging() { + setCharging(); + assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX)); + } + + @Test + public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount() { + setDischarging(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + mQuotaController.incrementJobCount(0, "com.android.test", 5); + assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); + } + + @Test + public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() { + setDischarging(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME; + mQuotaController.saveTimingSession(0, "com.android.test.spam", + createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25)); + mQuotaController.saveTimingSession(0, "com.android.test.spam", + createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount)); + mQuotaController.incrementJobCount(0, "com.android.test.spam", jobCount); + assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.spam", + WORKING_INDEX)); + + mQuotaController.saveTimingSession(0, "com.android.test.frequent", + createTimingSession(now - (2 * HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 2000)); + mQuotaController.saveTimingSession(0, "com.android.test.frequent", + createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500)); + assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.frequent", + FREQUENT_INDEX)); + } + + @Test + public void testIsWithinQuotaLocked_OverDuration_UnderJobCount() { + setDischarging(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5)); + mQuotaController.incrementJobCount(0, "com.android.test", 5); + assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); + } + + @Test + public void testIsWithinQuotaLocked_OverDuration_OverJobCount() { + setDischarging(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME; + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount)); + mQuotaController.incrementJobCount(0, "com.android.test", jobCount); + assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); + } + + @Test public void testMaybeScheduleCleanupAlarmLocked() { // No sessions saved yet. mQuotaController.maybeScheduleCleanupAlarmLocked(); @@ -752,6 +827,7 @@ public class QuotaControllerTest { // Active window size is 10 minutes. final int standbyBucket = ACTIVE_INDEX; + setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND); // No sessions saved yet. mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -1016,11 +1092,37 @@ public class QuotaControllerTest { .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX); - inOrder.verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), - any()); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class)); } + @Test + public void testMaybeScheduleStartAlarmLocked_JobCount_AllowedTime() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final int standbyBucket = WORKING_INDEX; + ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID, + SOURCE_PACKAGE, standbyBucket); + stats.jobCountInAllowedTime = + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME + 2; + + // Invalid time in the past, so the count shouldn't be used. + stats.jobCountExpirationTimeElapsed = + now - mQuotaController.getAllowedTimePerPeriodMs() / 2; + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); + verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + // Invalid time in the future, so the count should be used. + stats.jobCountExpirationTimeElapsed = + now + mQuotaController.getAllowedTimePerPeriodMs() / 2; + final long expectedWorkingAlarmTime = + stats.jobCountExpirationTimeElapsed + mQuotaController.getAllowedTimePerPeriodMs(); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); + verify(mAlarmManager, times(1)) + .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); + } /** * Tests that the start alarm is properly rescheduled if the earliest session that contributes @@ -1172,6 +1274,11 @@ public class QuotaControllerTest { mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = 5000; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 4000; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 3000; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 2000; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 500; mQuotaController.onConstantsUpdatedLocked(); @@ -1183,11 +1290,16 @@ public class QuotaControllerTest { mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]); assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); + assertEquals(500, mQuotaController.getMaxJobCountPerAllowedTime()); + assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]); + assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]); + assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]); + assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]); } @Test public void testConstantsUpdating_InvalidValues() { - // Test negatives + // Test negatives/too low. mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS; @@ -1195,6 +1307,11 @@ public class QuotaControllerTest { mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = -1; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 1; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 1; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 1; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 0; mQuotaController.onConstantsUpdatedLocked(); @@ -1205,6 +1322,11 @@ public class QuotaControllerTest { assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]); assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); + assertEquals(10, mQuotaController.getMaxJobCountPerAllowedTime()); + assertEquals(100, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]); + assertEquals(100, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]); + assertEquals(100, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]); + assertEquals(100, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]); // Test larger than a day. Controller should cap at one day. mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS; @@ -1246,6 +1368,7 @@ public class QuotaControllerTest { @Test public void testTimerTracking_Discharging() { setDischarging(); + setProcessState(ActivityManager.PROCESS_STATE_BACKUP); JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); @@ -1293,6 +1416,8 @@ public class QuotaControllerTest { */ @Test public void testTimerTracking_ChargingAndDischarging() { + setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); + JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2); @@ -1363,6 +1488,7 @@ public class QuotaControllerTest { @Test public void testTimerTracking_AllBackground() { setDischarging(); + setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); @@ -1503,6 +1629,64 @@ public class QuotaControllerTest { } /** + * Tests that Timers don't track job counts while in the foreground. + */ + @Test + public void testTimerTracking_JobCount_Foreground() { + setDischarging(); + + final int standbyBucket = ACTIVE_INDEX; + JobStatus jobFg1 = createJobStatus("testTimerTracking_JobCount_Foreground", 1); + JobStatus jobFg2 = createJobStatus("testTimerTracking_JobCount_Foreground", 2); + + mQuotaController.maybeStartTrackingJobLocked(jobFg1, null); + mQuotaController.maybeStartTrackingJobLocked(jobFg2, null); + assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID, + SOURCE_PACKAGE, standbyBucket); + assertEquals(0, stats.jobCountInAllowedTime); + + setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + mQuotaController.prepareForExecutionLocked(jobFg1); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.prepareForExecutionLocked(jobFg2); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false); + assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + + assertEquals(0, stats.jobCountInAllowedTime); + } + + /** + * Tests that Timers properly track job counts while in the background. + */ + @Test + public void testTimerTracking_JobCount_Background() { + final int standbyBucket = WORKING_INDEX; + JobStatus jobBg1 = createJobStatus("testTimerTracking_JobCount_Background", 1); + JobStatus jobBg2 = createJobStatus("testTimerTracking_JobCount_Background", 2); + mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); + mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); + + ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID, + SOURCE_PACKAGE, standbyBucket); + assertEquals(0, stats.jobCountInAllowedTime); + + setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); + mQuotaController.prepareForExecutionLocked(jobBg1); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.prepareForExecutionLocked(jobBg2); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobBg1, null, false); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); + + assertEquals(2, stats.jobCountInAllowedTime); + } + + /** * Tests that Timers properly track overlapping top and background jobs. */ @Test @@ -1680,6 +1864,7 @@ public class QuotaControllerTest { JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window + setProcessState(ActivityManager.PROCESS_STATE_HOME); // Now the package only has two seconds to run. final long remainingTimeMs = 2 * SECOND_IN_MILLIS; mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -1707,6 +1892,7 @@ public class QuotaControllerTest { JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window + setProcessState(ActivityManager.PROCESS_STATE_SERVICE); Handler handler = mQuotaController.getHandler(); spyOn(handler); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java index 5083110342f4..d91ce39ea92c 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java @@ -308,6 +308,24 @@ public class MagnificationGestureHandlerTest { assertZoomsImmediatelyOnSwipeFrom(STATE_SHORTCUT_TRIGGERED); } + @Test + public void testMultiTap_outOfDistanceSlop_shouldInIdle() { + // All delay motion events should be sent, if multi-tap with out of distance slop. + // STATE_IDLE will check if tapCount() < 2. + allowEventDelegation(); + assertStaysIn(STATE_IDLE, () -> { + tap(); + tap(DEFAULT_X * 2, DEFAULT_Y * 2); + }); + assertStaysIn(STATE_IDLE, () -> { + tap(); + tap(DEFAULT_X * 2, DEFAULT_Y * 2); + tap(); + tap(DEFAULT_X * 2, DEFAULT_Y * 2); + tap(); + }); + } + private void assertZoomsImmediatelyOnSwipeFrom(int state) { goFromStateIdleTo(state); swipeAndHold(); @@ -531,6 +549,11 @@ public class MagnificationGestureHandlerTest { send(upEvent()); } + private void tap(float x, float y) { + send(downEvent(x, y)); + send(upEvent(x, y)); + } + private void swipe() { swipeAndHold(); send(upEvent()); @@ -572,18 +595,26 @@ public class MagnificationGestureHandlerTest { } private MotionEvent downEvent() { + return downEvent(DEFAULT_X, DEFAULT_Y); + } + + private MotionEvent downEvent(float x, float y) { mLastDownTime = mClock.now(); return fromTouchscreen(MotionEvent.obtain(mLastDownTime, mLastDownTime, - ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0)); + ACTION_DOWN, x, y, 0)); } private MotionEvent upEvent() { - return upEvent(mLastDownTime); + return upEvent(DEFAULT_X, DEFAULT_Y, mLastDownTime); + } + + private MotionEvent upEvent(float x, float y) { + return upEvent(x, y, mLastDownTime); } - private MotionEvent upEvent(long downTime) { + private MotionEvent upEvent(float x, float y, long downTime) { return fromTouchscreen(MotionEvent.obtain(downTime, mClock.now(), - MotionEvent.ACTION_UP, DEFAULT_X, DEFAULT_Y, 0)); + MotionEvent.ACTION_UP, x, y, 0)); } private MotionEvent pointerEvent(int action, float x, float y) { diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 38e8ac2d8f4c..0813e6fa0252 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -206,6 +206,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_UID; setUpUserManager(); + + when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true); } private TransferOwnershipMetadataManager getMockTransferMetadataManager() { @@ -836,6 +838,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { MockUtils.checkIntent(intent), MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID)); } + /** * Test for: {@link DevicePolicyManager#setDeviceOwner} DO on system user installs successfully. */ @@ -2618,6 +2621,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().lockPatternUtils .isSeparateProfileChallengeEnabled(eq(PROFILE_USER))).thenReturn(false); dpmi.reportSeparateProfileChallengeChanged(PROFILE_USER); + when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true); verifyScreenTimeoutCall(Long.MAX_VALUE, PROFILE_USER); verifyScreenTimeoutCall(10L , UserHandle.USER_SYSTEM); @@ -4233,6 +4237,41 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertTrue(dpm.isActivePasswordSufficient()); } + public void testIsActivePasswordSufficient_noLockScreen() throws Exception { + // If there is no lock screen, the password is considered empty no matter what, because + // it provides no security. + when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(false); + + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + mContext.packageName = admin1.getPackageName(); + setupDeviceOwner(); + + // If no password requirements are set, isActivePasswordSufficient should succeed. + assertTrue(dpm.isActivePasswordSufficient()); + + // Now set some password quality requirements. + dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); + + reset(mContext.spiedContext); + final int userHandle = UserHandle.getUserId(mContext.binder.callingUid); + PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9, + 8, 2, + 6, 1, + 0, 1); + // This should be ignored, as there is no lock screen. + dpm.setActivePasswordState(passwordMetricsNoSymbols, userHandle); + dpm.reportPasswordChanged(userHandle); + + // No broadcast should be sent. + verify(mContext.spiedContext, times(0)).sendBroadcastAsUser( + MockUtils.checkIntentAction(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED), + MockUtils.checkUserHandle(userHandle)); + + // The active (nonexistent) password doesn't comply with the requirements. + assertFalse(dpm.isActivePasswordSufficient()); + } + private void setActivePasswordState(PasswordMetrics passwordMetrics) throws Exception { final int userHandle = UserHandle.getUserId(mContext.binder.callingUid); diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java index 0328621ce5d1..8afc3d30efa3 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java @@ -22,7 +22,6 @@ import static android.view.Display.INVALID_DISPLAY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -58,8 +57,7 @@ public class InputMethodManagerServiceTests { // Make sure that there is a short-circuit for DEFAULT_DISPLAY. assertEquals(DEFAULT_DISPLAY, InputMethodManagerService.computeImeDisplayIdForTarget( - DEFAULT_DISPLAY, false /* isVrImeStarted */, - sMustNotBeCalledChecker)); + DEFAULT_DISPLAY, sMustNotBeCalledChecker)); } @Test @@ -67,17 +65,7 @@ public class InputMethodManagerServiceTests { // Make sure that there is a short-circuit for INVALID_DISPLAY. assertEquals(DEFAULT_DISPLAY, InputMethodManagerService.computeImeDisplayIdForTarget( - INVALID_DISPLAY, false /* isVrImeStarted */, - sMustNotBeCalledChecker)); - } - - @Test - public void testComputeImeDisplayId_VrIme() { - // Make sure that there is a short-circuit for VR IME. - assertEquals(DEFAULT_DISPLAY, - InputMethodManagerService.computeImeDisplayIdForTarget( - SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, true /* isVrImeStarted */, - sMustNotBeCalledChecker)); + INVALID_DISPLAY, sMustNotBeCalledChecker)); } @Test @@ -86,8 +74,7 @@ public class InputMethodManagerServiceTests { // Make sure IME displayId is DEFAULT_DISPLAY. assertEquals(DEFAULT_DISPLAY, InputMethodManagerService.computeImeDisplayIdForTarget( - NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, false /* isVrImeStarted */, - sChecker)); + NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker)); } @Test @@ -96,7 +83,6 @@ public class InputMethodManagerServiceTests { // Make sure IME displayId is the same display. assertEquals(SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, InputMethodManagerService.computeImeDisplayIdForTarget( - SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, false /* isVrImeStarted */, - sChecker)); + SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker)); } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java index 2dc3510a82e5..cf89cb8f7a15 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -37,8 +37,8 @@ import android.os.FileUtils; import android.os.IProgressListener; import android.os.RemoteException; import android.os.UserManager; -import android.os.storage.StorageManager; import android.os.storage.IStorageManager; +import android.os.storage.StorageManager; import android.security.KeyStore; import android.test.AndroidTestCase; @@ -46,6 +46,7 @@ import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockSettingsInternal; import com.android.server.LocalServices; +import com.android.server.wm.WindowManagerInternal; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -85,6 +86,8 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { KeyStore mKeyStore; MockSyntheticPasswordManager mSpManager; IAuthSecret mAuthSecretService; + WindowManagerInternal mMockWindowManager; + protected boolean mHasSecureLockScreen; @Override protected void setUp() throws Exception { @@ -97,10 +100,13 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { mActivityManager = mock(IActivityManager.class); mDevicePolicyManager = mock(DevicePolicyManager.class); mDevicePolicyManagerInternal = mock(DevicePolicyManagerInternal.class); + mMockWindowManager = mock(WindowManagerInternal.class); LocalServices.removeServiceForTest(LockSettingsInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal); + LocalServices.addService(WindowManagerInternal.class, mMockWindowManager); mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager, mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class), @@ -114,11 +120,17 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { storageDir.mkdirs(); } + mHasSecureLockScreen = true; mLockPatternUtils = new LockPatternUtils(mContext) { @Override public ILockSettings getLockSettings() { return mService; } + + @Override + public boolean hasSecureLockScreen() { + return mHasSecureLockScreen; + } }; mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService, mUserManager); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java index e12f6d3be71e..5124803ee298 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java @@ -26,13 +26,12 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSW import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; import android.os.RemoteException; -import android.os.UserHandle; import android.service.gatekeeper.GateKeeperResponse; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.VerifyCredentialResponse; -import com.android.server.locksettings.LockSettingsStorage.CredentialHash; import com.android.server.locksettings.FakeGateKeeperService.VerifyHandle; +import com.android.server.locksettings.LockSettingsStorage.CredentialHash; /** * runtest frameworks-services -c com.android.server.locksettings.LockSettingsServiceTests @@ -54,11 +53,21 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { PASSWORD_QUALITY_ALPHABETIC); } + public void testCreatePasswordFailsWithoutLockScreen() throws RemoteException { + testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "password", + CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC); + } + public void testCreatePatternPrimaryUser() throws RemoteException { testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING); } + public void testCreatePatternFailsWithoutLockScreen() throws RemoteException { + testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "123456789", + CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING); + } + public void testChangePasswordPrimaryUser() throws RemoteException { testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN, "asdfghjk", CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC); @@ -198,6 +207,21 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { assertVerifyCredentials(userId, credential, type, -1); } + private void testCreateCredentialFailsWithoutLockScreen( + int userId, String credential, int type, int quality) throws RemoteException { + mHasSecureLockScreen = false; + + try { + mService.setLockCredential(credential, type, null, quality, userId); + fail("An exception should have been thrown."); + } catch (UnsupportedOperationException e) { + // Success - the exception was expected. + } + + assertFalse(mService.havePassword(userId)); + assertFalse(mService.havePattern(userId)); + } + private void testChangeCredentials(int userId, String newCredential, int newType, String oldCredential, int oldType, int quality) throws RemoteException { final long sid = 1234; diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java index a28a5a10e832..929c3b525db9 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java @@ -27,6 +27,7 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.ActivityManager; @@ -77,6 +78,7 @@ public class LockSettingsShellCommandTest { final Context context = InstrumentationRegistry.getTargetContext(); mUserId = ActivityManager.getCurrentUser(); mCommand = new LockSettingsShellCommand(mLockPatternUtils); + when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true); } @Test @@ -103,6 +105,16 @@ public class LockSettingsShellCommandTest { } @Test + public void testChangePin_noLockScreen() throws Exception { + when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false); + assertEquals(-1, mCommand.exec(new Binder(), in, out, err, + new String[] { "set-pin", "--old", "1234", "4321" }, + mShellCallback, mResultReceiver)); + verify(mLockPatternUtils).hasSecureLockScreen(); + verifyNoMoreInteractions(mLockPatternUtils); + } + + @Test public void testChangePassword() throws Exception { when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false); when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true); @@ -115,6 +127,16 @@ public class LockSettingsShellCommandTest { } @Test + public void testChangePassword_noLockScreen() throws Exception { + when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false); + assertEquals(-1, mCommand.exec(new Binder(), in, out, err, + new String[] { "set-password", "--old", "1234", "4321" }, + mShellCallback, mResultReceiver)); + verify(mLockPatternUtils).hasSecureLockScreen(); + verifyNoMoreInteractions(mLockPatternUtils); + } + + @Test public void testChangePattern() throws Exception { when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true); when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false); @@ -126,6 +148,16 @@ public class LockSettingsShellCommandTest { } @Test + public void testChangePattern_noLockScreen() throws Exception { + when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false); + assertEquals(-1, mCommand.exec(new Binder(), in, out, err, + new String[] { "set-pattern", "--old", "1234", "4321" }, + mShellCallback, mResultReceiver)); + verify(mLockPatternUtils).hasSecureLockScreen(); + verifyNoMoreInteractions(mLockPatternUtils); + } + + @Test public void testClear() throws Exception { when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true); when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java index 94e02bc4d35f..0595a5b2e9a0 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java @@ -40,10 +40,10 @@ import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationRe import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken; import com.android.server.locksettings.SyntheticPasswordManager.PasswordData; -import java.util.ArrayList; - import org.mockito.ArgumentCaptor; +import java.util.ArrayList; + /** * runtest frameworks-services -c com.android.server.locksettings.SyntheticPasswordTests @@ -448,6 +448,37 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); } + public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception { + final String password = "password"; + final String pattern = "123654"; + final String token = "some-high-entropy-secure-token"; + + mHasSecureLockScreen = false; + enableSyntheticPassword(); + long handle = mLocalService.addEscrowToken(token.getBytes(), PRIMARY_USER_ID); + assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + + try { + mLocalService.setLockCredentialWithToken(password, + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, token.getBytes(), + PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID); + fail("An exception should have been thrown."); + } catch (UnsupportedOperationException e) { + // Success - the exception was expected. + } + assertFalse(mService.havePassword(PRIMARY_USER_ID)); + + try { + mLocalService.setLockCredentialWithToken(pattern, + LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, token.getBytes(), + PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID); + fail("An exception should have been thrown."); + } catch (UnsupportedOperationException e) { + // Success - the exception was expected. + } + assertFalse(mService.havePattern(PRIMARY_USER_ID)); + } + public void testgetHashFactorPrimaryUser() throws RemoteException { final String password = "password"; mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java index f817e8e33b31..6da202b93065 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java @@ -16,8 +16,6 @@ package com.android.server.pm.dex; -import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.atMost; @@ -26,10 +24,12 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; +import android.os.UserHandle; import android.os.storage.StorageManager; import androidx.test.filters.SmallTest; @@ -56,40 +56,44 @@ import org.mockito.stubbing.Stubber; public class DexLoggerTests { private static final String OWNING_PACKAGE_NAME = "package.name"; private static final String VOLUME_UUID = "volUuid"; - private static final String DEX_PATH = "/bar/foo.jar"; + private static final String FILE_PATH = "/bar/foo.jar"; private static final int STORAGE_FLAGS = StorageManager.FLAG_STORAGE_DE; private static final int OWNER_UID = 43; private static final int OWNER_USER_ID = 44; // Obtained via: echo -n "foo.jar" | sha256sum - private static final String DEX_FILENAME_HASH = + private static final String FILENAME_HASH = "91D7B844D7CC9673748FF057D8DC83972280FC28537D381AA42015A9CF214B9F"; - private static final byte[] CONTENT_HASH_BYTES = new byte[] { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 + private static final byte[] CONTENT_HASH_BYTES = new byte[]{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 }; private static final String CONTENT_HASH = "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20"; private static final byte[] EMPTY_BYTES = {}; - @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); + private static final String EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH = + "dcl:" + FILENAME_HASH; + private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH = + EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH + " " + CONTENT_HASH; + private static final String EXPECTED_MESSAGE_NATIVE_WITH_CONTENT_HASH = + "dcln:" + FILENAME_HASH + " " + CONTENT_HASH; + + @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.LENIENT); @Mock IPackageManager mPM; @Mock Installer mInstaller; - private PackageDynamicCodeLoading mPackageDynamicCodeLoading; private DexLogger mDexLogger; private final ListMultimap<Integer, String> mMessagesForUid = ArrayListMultimap.create(); private boolean mWriteTriggered = false; - private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH = - DEX_FILENAME_HASH + " " + CONTENT_HASH; @Before public void setup() throws Exception { // Disable actually attempting to do file writes. - mPackageDynamicCodeLoading = new PackageDynamicCodeLoading() { + PackageDynamicCodeLoading packageDynamicCodeLoading = new PackageDynamicCodeLoading() { @Override void maybeWriteAsync() { mWriteTriggered = true; @@ -102,13 +106,13 @@ public class DexLoggerTests { }; // For test purposes capture log messages as well as sending to the event log. - mDexLogger = new DexLogger(mPM, mInstaller, mPackageDynamicCodeLoading) { + mDexLogger = new DexLogger(mPM, mInstaller, packageDynamicCodeLoading) { @Override - void writeDclEvent(int uid, String message) { - super.writeDclEvent(uid, message); - mMessagesForUid.put(uid, message); - } - }; + void writeDclEvent(String subtag, int uid, String message) { + super.writeDclEvent(subtag, uid, message); + mMessagesForUid.put(uid, subtag + ":" + message); + } + }; // Make the owning package exist in our mock PackageManager. ApplicationInfo appInfo = new ApplicationInfo(); @@ -124,9 +128,9 @@ public class DexLoggerTests { @Test public void testOneLoader_ownFile_withFileHash() throws Exception { - whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); + whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES)); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID); @@ -139,13 +143,13 @@ public class DexLoggerTests { @Test public void testOneLoader_ownFile_noFileHash() throws Exception { - whenFileIsHashed(DEX_PATH, doReturn(EMPTY_BYTES)); + whenFileIsHashed(FILE_PATH, doReturn(EMPTY_BYTES)); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID); - assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH); + assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH); // File should be removed from the DCL list, since we can't hash it. assertThat(mWriteTriggered).isTrue(); @@ -154,13 +158,14 @@ public class DexLoggerTests { @Test public void testOneLoader_ownFile_hashingFails() throws Exception { - whenFileIsHashed(DEX_PATH, doThrow(new InstallerException("Intentional failure for test"))); + whenFileIsHashed(FILE_PATH, + doThrow(new InstallerException("Intentional failure for test"))); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID); - assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH); + assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH); // File should be removed from the DCL list, since we can't hash it. assertThat(mWriteTriggered).isTrue(); @@ -178,11 +183,23 @@ public class DexLoggerTests { } @Test + public void testOneLoader_pathTraversal() throws Exception { + String filePath = "/bar/../secret/foo.jar"; + whenFileIsHashed(filePath, doReturn(CONTENT_HASH_BYTES)); + setPackageUid(OWNING_PACKAGE_NAME, -1); + + recordLoad(OWNING_PACKAGE_NAME, filePath); + mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); + + assertThat(mMessagesForUid).isEmpty(); + } + + @Test public void testOneLoader_differentOwner() throws Exception { - whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); + whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES)); setPackageUid("other.package.name", 1001); - recordLoad("other.package.name", DEX_PATH); + recordLoad("other.package.name", FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid.keys()).containsExactly(1001); @@ -192,10 +209,10 @@ public class DexLoggerTests { @Test public void testOneLoader_differentOwner_uninstalled() throws Exception { - whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); + whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES)); setPackageUid("other.package.name", -1); - recordLoad("other.package.name", DEX_PATH); + recordLoad("other.package.name", FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid).isEmpty(); @@ -203,22 +220,38 @@ public class DexLoggerTests { } @Test + public void testNativeCodeLoad() throws Exception { + whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES)); + + recordLoadNative(FILE_PATH); + mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); + + assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID); + assertThat(mMessagesForUid) + .containsEntry(OWNER_UID, EXPECTED_MESSAGE_NATIVE_WITH_CONTENT_HASH); + + assertThat(mWriteTriggered).isFalse(); + assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()) + .containsExactly(OWNING_PACKAGE_NAME); + } + + @Test public void testMultipleLoadersAndFiles() throws Exception { String otherDexPath = "/bar/nosuchdir/foo.jar"; - whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); + whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES)); whenFileIsHashed(otherDexPath, doReturn(EMPTY_BYTES)); setPackageUid("other.package.name1", 1001); setPackageUid("other.package.name2", 1002); - recordLoad("other.package.name1", DEX_PATH); + recordLoad("other.package.name1", FILE_PATH); recordLoad("other.package.name1", otherDexPath); - recordLoad("other.package.name2", DEX_PATH); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad("other.package.name2", FILE_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid.keys()).containsExactly(1001, 1001, 1002, OWNER_UID); assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITH_CONTENT_HASH); - assertThat(mMessagesForUid).containsEntry(1001, DEX_FILENAME_HASH); + assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH); assertThat(mMessagesForUid).containsEntry(1002, EXPECTED_MESSAGE_WITH_CONTENT_HASH); assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITH_CONTENT_HASH); @@ -233,7 +266,7 @@ public class DexLoggerTests { @Test public void testUnknownOwner() { reset(mPM); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading("other.package.name"); assertThat(mMessagesForUid).isEmpty(); @@ -244,7 +277,7 @@ public class DexLoggerTests { @Test public void testUninstalledPackage() { reset(mPM); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid).isEmpty(); @@ -262,7 +295,16 @@ public class DexLoggerTests { } private void recordLoad(String loadingPackageName, String dexPath) { - mPackageDynamicCodeLoading.record( - OWNING_PACKAGE_NAME, dexPath, FILE_TYPE_DEX, OWNER_USER_ID, loadingPackageName); + mDexLogger.recordDex(OWNER_USER_ID, dexPath, OWNING_PACKAGE_NAME, loadingPackageName); + mWriteTriggered = false; + } + + private void recordLoadNative(String nativePath) throws Exception { + int loadingUid = UserHandle.getUid(OWNER_USER_ID, OWNER_UID); + String[] packageNames = { OWNING_PACKAGE_NAME }; + when(mPM.getPackagesForUid(loadingUid)).thenReturn(packageNames); + + mDexLogger.recordNative(loadingUid, nativePath); + mWriteTriggered = false; } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 319ffed3778c..8be63fc43adb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -31,7 +31,6 @@ import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT; import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT; import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING; import static com.android.server.wm.ActivityStack.ActivityState.PAUSING; -import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPED; import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING; @@ -76,9 +75,6 @@ public class ActivityRecordTests extends ActivityTestsBase { mStack = (TestActivityStack) new StackBuilder(mRootActivityContainer).build(); mTask = mStack.getChildAt(0); mActivity = mTask.getTopActivity(); - - doReturn(false).when(mService).isBooting(); - doReturn(true).when(mService).isBooted(); } @Test @@ -121,23 +117,22 @@ public class ActivityRecordTests extends ActivityTestsBase { mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped"); - // The activity is in the focused stack so it should be resumed. + // The activity is in the focused stack so it should not move to paused. mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); - assertTrue(mActivity.isState(RESUMED)); + assertTrue(mActivity.isState(STOPPED)); assertFalse(pauseFound.value); - // Make the activity non focusable - mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped"); - doReturn(false).when(mActivity).isFocusable(); + // Clear focused stack + final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay(); + when(display.getFocusedStack()).thenReturn(null); - // If the activity is not focusable, it should move to paused. + // In the unfocused stack, the activity should move to paused. mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); assertTrue(mActivity.isState(PAUSING)); assertTrue(pauseFound.value); // Make sure that the state does not change for current non-stopping states. mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped"); - doReturn(true).when(mActivity).isFocusable(); mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index ea8f33f0c630..68df87e3e27d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -55,7 +55,6 @@ import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; import android.os.Looper; -import android.os.PowerManager; import android.os.Process; import android.os.UserHandle; import android.service.voice.IVoiceInteractionSession; @@ -426,7 +425,6 @@ class ActivityTestsBase { doReturn(mock(IPackageManager.class)).when(this).getPackageManager(); // allow background activity starts by default doReturn(true).when(this).isBackgroundActivityStartsEnabled(); - doNothing().when(this).updateCpuStats(); } void setup(IntentFirewall intentFirewall, PendingIntentController intentController, @@ -582,8 +580,6 @@ class ActivityTestsBase { doNothing().when(this).acquireLaunchWakelock(); doReturn(mKeyguardController).when(this).getKeyguardController(); - mLaunchingActivity = mock(PowerManager.WakeLock.class); - initialize(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java index d0b9225715c4..ea5ab7bf0621 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java @@ -70,9 +70,9 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); verify(mTransaction).reparent(eq(mToken.getSurfaceControl()), - eq(mToken.mSurfaceAnimator.mLeash.getHandle())); + eq(mToken.mSurfaceAnimator.mLeash)); verify(mTransaction).reparent(eq(mToken.mSurfaceAnimator.mLeash), - eq(mToken.mAnimationBoundsLayer.getHandle())); + eq(mToken.mAnimationBoundsLayer)); } @Test @@ -111,7 +111,7 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); verify(mTransaction).reparent(eq(mToken.getSurfaceControl()), - eq(mToken.mSurfaceAnimator.mLeash.getHandle())); + eq(mToken.mSurfaceAnimator.mLeash)); assertThat(mToken.mAnimationBoundsLayer).isNull(); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java index ad80cd6ddfb7..9b84215a8f3b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java @@ -90,7 +90,7 @@ public class SurfaceAnimatorTest extends WindowTestsBase { final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass( OnAnimationFinishedCallback.class); assertAnimating(mAnimatable); - verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash.getHandle())); + verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash)); verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); callbackCaptor.getValue().onAnimationFinished(mSpec); diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 6c4b1af8c2a1..0fe5e080d1f8 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -793,15 +793,17 @@ public class TelecomManager { * <p> * Apps must be prepared for this method to return {@code null}, indicating that there currently * exists no user-chosen default {@code PhoneAccount}. + * <p> + * The default dialer has access to use this method. * * @return The user outgoing phone account selected by the user. - * @hide */ - @UnsupportedAppUsage + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() { try { if (isServiceConnected()) { - return getTelecomService().getUserSelectedOutgoingPhoneAccount(); + return getTelecomService().getUserSelectedOutgoingPhoneAccount( + mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getUserSelectedOutgoingPhoneAccount", e); @@ -810,10 +812,14 @@ public class TelecomManager { } /** - * Sets the user-chosen default for making outgoing phone calls. + * Sets the user-chosen default {@link PhoneAccountHandle} for making outgoing phone calls. + * + * @param accountHandle The {@link PhoneAccountHandle} which will be used by default for making + * outgoing voice calls. * @hide */ - @UnsupportedAppUsage + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + @SystemApi public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) { try { if (isServiceConnected()) { diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index 954a7098f6be..e1d5c17d5e3a 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -45,7 +45,7 @@ interface ITelecomService { /** * @see TelecomServiceImpl#getUserSelectedOutgoingPhoneAccount */ - PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(); + PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(String callingPackage); /** * @see TelecomServiceImpl#setUserSelectedOutgoingPhoneAccount diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 0e5c71d6ef90..0fa1b41d4b16 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -129,6 +129,66 @@ public class EuiccManager { "android.telephony.euicc.action.RESOLVE_ERROR"; /** + * Intent action sent by system apps (such as the Settings app) to the Telephony framework to + * enable or disable a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and + * {@link #EXTRA_ENABLE_SUBSCRIPTION}. + * + * <p>Unlike {@link #switchToSubscription(int, PendingIntent)}, using this action allows the + * underlying eUICC service (i.e. the LPA app) to control the UI experience during this + * operation. The action is received by the Telephony framework, which in turn selects and + * launches an appropriate LPA activity to present UI to the user. For example, the activity may + * show a confirmation dialog, a progress dialog, or an error dialog when necessary. + * + * <p>The launched activity will immediately finish with + * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false. + * + * @hide + */ + @SystemApi + public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = + "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED"; + + /** + * Intent action sent by system apps (such as the Settings app) to the Telephony framework to + * delete a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID}. + * + * <p>Unlike {@link #deleteSubscription(int, PendingIntent)}, using this action allows the + * underlying eUICC service (i.e. the LPA app) to control the UI experience during this + * operation. The action is received by the Telephony framework, which in turn selects and + * launches an appropriate LPA activity to present UI to the user. For example, the activity may + * show a confirmation dialog, a progress dialog, or an error dialog when necessary. + * + * <p>The launched activity will immediately finish with + * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false. + * + * @hide + */ + @SystemApi + public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = + "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED"; + + /** + * Intent action sent by system apps (such as the Settings app) to the Telephony framework to + * rename a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and + * {@link #EXTRA_SUBSCRIPTION_NICKNAME}. + * + * <p>Unlike {@link #updateSubscriptionNickname(int, String, PendingIntent)}, using this action + * allows the the underlying eUICC service (i.e. the LPA app) to control the UI experience + * during this operation. The action is received by the Telephony framework, which in turn + * selects and launches an appropriate LPA activity to present UI to the user. For example, the + * activity may show a confirmation dialog, a progress dialog, or an error dialog when + * necessary. + * + * <p>The launched activity will immediately finish with + * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false. + * + * @hide + */ + @SystemApi + public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = + "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED"; + + /** * Result code for an operation indicating that the operation succeeded. */ public static final int EMBEDDED_SUBSCRIPTION_RESULT_OK = 0; @@ -219,6 +279,37 @@ public class EuiccManager { "android.telephony.euicc.extra.FORCE_PROVISION"; /** + * Key for an extra set on privileged actions {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED}, + * {@link #ACTION_DELETE_SUBSCRIPTION_PRIVILEGED}, and + * {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing the ID of the targeted subscription. + * + * @hide + */ + @SystemApi + public static final String EXTRA_SUBSCRIPTION_ID = + "android.telephony.euicc.extra.SUBSCRIPTION_ID"; + + /** + * Key for an extra set on {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED} providing a boolean + * value of whether to enable or disable the targeted subscription. + * + * @hide + */ + @SystemApi + public static final String EXTRA_ENABLE_SUBSCRIPTION = + "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION"; + + /** + * Key for an extra set on {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing a new + * nickname for the targeted subscription. + * + * @hide + */ + @SystemApi + public static final String EXTRA_SUBSCRIPTION_NICKNAME = + "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME"; + + /** * Optional meta-data attribute for a carrier app providing an icon to use to represent the * carrier. If not provided, the app's launcher icon will be used as a fallback. */ diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk index ee2ec0a80b03..979d13ac9405 100644 --- a/tests/DexLoggerIntegrationTests/Android.mk +++ b/tests/DexLoggerIntegrationTests/Android.mk @@ -29,6 +29,35 @@ include $(BUILD_JAVA_LIBRARY) dexloggertest_jar := $(LOCAL_BUILT_MODULE) +# Also build a native library that the test app can dynamically load + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE := DexLoggerNativeTestLibrary +LOCAL_MULTILIB := first +LOCAL_SRC_FILES := src/cpp/com_android_dcl_Jni.cpp +LOCAL_C_INCLUDES += \ + $(JNI_H_INCLUDE) +LOCAL_SDK_VERSION := 28 +LOCAL_NDK_STL_VARIANT := c++_static + +include $(BUILD_SHARED_LIBRARY) + +dexloggertest_so := $(LOCAL_BUILT_MODULE) + +# And a standalone native executable that we can exec. + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE := DexLoggerNativeExecutable +LOCAL_SRC_FILES := src/cpp/test_executable.cpp + +include $(BUILD_EXECUTABLE) + +dexloggertest_executable := $(LOCAL_BUILT_MODULE) + # Build the test app itself include $(CLEAR_VARS) @@ -37,14 +66,18 @@ LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := DexLoggerIntegrationTests LOCAL_SDK_VERSION := current LOCAL_COMPATIBILITY_SUITE := device-tests -LOCAL_CERTIFICATE := platform +LOCAL_CERTIFICATE := shared LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm) LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-test \ truth-prebuilt \ -# This gets us the javalib.jar built by DexLoggerTestLibrary above. -LOCAL_JAVA_RESOURCE_FILES := $(dexloggertest_jar) +# This gets us the javalib.jar built by DexLoggerTestLibrary above as well as the various +# native binaries. +LOCAL_JAVA_RESOURCE_FILES := \ + $(dexloggertest_jar) \ + $(dexloggertest_so) \ + $(dexloggertest_executable) include $(BUILD_PACKAGE) diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java index 75ee0896c23a..d68769b378b9 100644 --- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java +++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java @@ -17,6 +17,7 @@ package com.android.server.pm.dex; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import android.app.UiAutomation; import android.content.Context; @@ -25,6 +26,7 @@ import android.os.SystemClock; import android.support.test.InstrumentationRegistry; import android.support.test.filters.LargeTest; import android.util.EventLog; +import android.util.EventLog.Event; import dalvik.system.DexClassLoader; @@ -65,14 +67,13 @@ public final class DexLoggerIntegrationTests { // Event log tag used for SNET related events private static final int SNET_TAG = 0x534e4554; - // Subtag used to distinguish dynamic code loading events - private static final String DCL_SUBTAG = "dcl"; + // Subtags used to distinguish dynamic code loading events + private static final String DCL_DEX_SUBTAG = "dcl"; + private static final String DCL_NATIVE_SUBTAG = "dcln"; - // All the tags we care about - private static final int[] TAG_LIST = new int[] { SNET_TAG }; - - // This is {@code DynamicCodeLoggingService#JOB_ID} - private static final int DYNAMIC_CODE_LOGGING_JOB_ID = 2030028; + // These are job IDs from DynamicCodeLoggingService + private static final int IDLE_LOGGING_JOB_ID = 2030028; + private static final int AUDIT_WATCHING_JOB_ID = 203142925; private static Context sContext; private static int sMyUid; @@ -89,15 +90,20 @@ public final class DexLoggerIntegrationTests { // Without this the first test passes and others don't - we don't see new events in the // log. The exact reason is unclear. EventLog.writeEvent(SNET_TAG, "Dummy event"); + + // Audit log messages are throttled by the kernel (at the request of logd) to 5 per + // second, so running the tests too quickly in sequence means we lose some and get + // spurious failures. Sigh. + SystemClock.sleep(1000); } @Test - public void testDexLoggerGeneratesEvents() throws Exception { - File privateCopyFile = fileForJar("copied.jar"); + public void testDexLoggerGeneratesEvents_standardClassLoader() throws Exception { + File privateCopyFile = privateFile("copied.jar"); // Obtained via "echo -n copied.jar | sha256sum" String expectedNameHash = "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C"; - String expectedContentHash = copyAndHashJar(privateCopyFile); + String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile); // Feed the jar to a class loader and make sure it contains what we expect. ClassLoader parentClassLoader = sContext.getClass().getClassLoader(); @@ -107,18 +113,18 @@ public final class DexLoggerIntegrationTests { // And make sure we log events about it long previousEventNanos = mostRecentEventTimeNanos(); - runDexLogger(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); - assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash); + assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG, + expectedNameHash, expectedContentHash); } @Test - public void testDexLoggerGeneratesEvents_unknownClassLoader() throws Exception { - File privateCopyFile = fileForJar("copied2.jar"); + File privateCopyFile = privateFile("copied2.jar"); String expectedNameHash = "202158B6A3169D78F1722487205A6B036B3F2F5653FDCFB4E74710611AC7EB93"; - String expectedContentHash = copyAndHashJar(privateCopyFile); + String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile); // This time make sure an unknown class loader is an ancestor of the class loader we use. ClassLoader knownClassLoader = sContext.getClass().getClassLoader(); @@ -129,22 +135,185 @@ public final class DexLoggerIntegrationTests { // And make sure we log events about it long previousEventNanos = mostRecentEventTimeNanos(); - runDexLogger(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG, + expectedNameHash, expectedContentHash); + } + + @Test + public void testDexLoggerGeneratesEvents_nativeLibrary() throws Exception { + File privateCopyFile = privateFile("copied.so"); + String expectedNameHash = + "996223BAD4B4FE75C57A3DEC61DB9C0B38E0A7AD479FC95F33494F4BC55A0F0E"; + String expectedContentHash = + copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile); + + System.load(privateCopyFile.toString()); + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); - assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash); + assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, + expectedNameHash, expectedContentHash); } - private static File fileForJar(String name) { - return new File(sContext.getDir("jars", Context.MODE_PRIVATE), name); + @Test + public void testDexLoggerGeneratesEvents_nativeLibrary_escapedName() throws Exception { + // A file name with a space will be escaped in the audit log; verify we un-escape it + // correctly. + File privateCopyFile = privateFile("second copy.so"); + String expectedNameHash = + "8C39990C560B4F36F83E208E279F678746FE23A790E4C50F92686584EA2041CA"; + String expectedContentHash = + copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile); + + System.load(privateCopyFile.toString()); + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, + expectedNameHash, expectedContentHash); } - private static String copyAndHashJar(File copyTo) throws Exception { + @Test + public void testDexLoggerGeneratesEvents_nativeExecutable() throws Exception { + File privateCopyFile = privateFile("test_executable"); + String expectedNameHash = + "3FBEC3F925A132D18F347F11AE9A5BB8DE1238828F8B4E064AA86EB68BD46DCF"; + String expectedContentHash = + copyAndHashResource("/DexLoggerNativeExecutable", privateCopyFile); + assertThat(privateCopyFile.setExecutable(true)).isTrue(); + + Process process = Runtime.getRuntime().exec(privateCopyFile.toString()); + int exitCode = process.waitFor(); + assertThat(exitCode).isEqualTo(0); + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, + expectedNameHash, expectedContentHash); + } + + @Test + public void testDexLoggerGeneratesEvents_spoofed_validFile() throws Exception { + File privateCopyFile = privateFile("spoofed"); + + String expectedContentHash = + copyAndHashResource("/DexLoggerNativeExecutable", privateCopyFile); + + EventLog.writeEvent(EventLog.getTagCode("auditd"), + "type=1400 avc: granted { execute_no_trans } " + + "path=\"" + privateCopyFile + "\" " + + "scontext=u:r:untrusted_app_27: " + + "tcontext=u:object_r:app_data_file: " + + "tclass=file "); + + String expectedNameHash = + "1CF36F503A02877BB775DC23C1C5A47A95F2684B6A1A83B11795B856D88861E3"; + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, + expectedNameHash, expectedContentHash); + } + + @Test + public void testDexLoggerGeneratesEvents_spoofed_pathTraversal() throws Exception { + File privateDir = privateFile("x").getParentFile(); + + // Transform /a/b/c -> /a/b/c/../../.. so we get back to the root + File pathTraversalToRoot = privateDir; + File root = new File("/"); + while (!privateDir.equals(root)) { + pathTraversalToRoot = new File(pathTraversalToRoot, ".."); + privateDir = privateDir.getParentFile(); + } + + File spoofedFile = new File(pathTraversalToRoot, "dev/urandom"); + + assertWithMessage("Expected " + spoofedFile + " to be readable") + .that(spoofedFile.canRead()).isTrue(); + + EventLog.writeEvent(EventLog.getTagCode("auditd"), + "type=1400 avc: granted { execute_no_trans } " + + "path=\"" + spoofedFile + "\" " + + "scontext=u:r:untrusted_app_27: " + + "tcontext=u:object_r:app_data_file: " + + "tclass=file "); + + String expectedNameHash = + "65528FE876BD676B0DFCC9A8ACA8988E026766F99EEC1E1FB48F46B2F635E225"; + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then trigger generating DCL events + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash); + } + + @Test + public void testDexLoggerGeneratesEvents_spoofed_otherAppFile() throws Exception { + File ourPath = sContext.getDatabasePath("android_pay"); + File targetPath = new File(ourPath.toString() + .replace("com.android.frameworks.dexloggertest", "com.google.android.gms")); + + assertWithMessage("Expected " + targetPath + " to not be readable") + .that(targetPath.canRead()).isFalse(); + + EventLog.writeEvent(EventLog.getTagCode("auditd"), + "type=1400 avc: granted { execute_no_trans } " + + "path=\"" + targetPath + "\" " + + "scontext=u:r:untrusted_app_27: " + + "tcontext=u:object_r:app_data_file: " + + "tclass=file "); + + String expectedNameHash = + "CBE04E8AB9E7199FC19CBAAF9C774B88E56B3B19E823F2251693380AD6F515E6"; + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then trigger generating DCL events + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash); + } + + private static File privateFile(String name) { + return new File(sContext.getDir("dcl", Context.MODE_PRIVATE), name); + } + + private static String copyAndHashResource(String resourcePath, File copyTo) throws Exception { MessageDigest hasher = MessageDigest.getInstance("SHA-256"); // Copy the jar from our Java resources to a private data directory Class<?> thisClass = DexLoggerIntegrationTests.class; - try (InputStream input = thisClass.getResourceAsStream("/javalib.jar"); - OutputStream output = new FileOutputStream(copyTo)) { + try (InputStream input = thisClass.getResourceAsStream(resourcePath); + OutputStream output = new FileOutputStream(copyTo)) { byte[] buffer = new byte[1024]; while (true) { int numRead = input.read(buffer); @@ -166,24 +335,18 @@ public final class DexLoggerIntegrationTests { return formatter.toString(); } - private static long mostRecentEventTimeNanos() throws Exception { - List<EventLog.Event> events = new ArrayList<>(); - - EventLog.readEvents(TAG_LIST, events); - return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos(); - } - - private static void runDexLogger() throws Exception { - // This forces {@code DynamicCodeLoggingService} to start now. - runCommand("cmd jobscheduler run -f android " + DYNAMIC_CODE_LOGGING_JOB_ID); + private static void runDynamicCodeLoggingJob(int jobId) throws Exception { + // This forces the DynamicCodeLoggingService job to start now. + runCommand("cmd jobscheduler run -f android " + jobId); // Wait for the job to have run. long startTime = SystemClock.elapsedRealtime(); while (true) { String response = runCommand( - "cmd jobscheduler get-job-state android " + DYNAMIC_CODE_LOGGING_JOB_ID); + "cmd jobscheduler get-job-state android " + jobId); if (!response.contains("pending") && !response.contains("active")) { break; } + // Don't wait forever - if it's taken > 10s then something is very wrong. if (SystemClock.elapsedRealtime() - startTime > TimeUnit.SECONDS.toMillis(10)) { throw new AssertionError("Job has not completed: " + response); } @@ -208,37 +371,68 @@ public final class DexLoggerIntegrationTests { return response.toString("UTF-8"); } - private static void assertDclLoggedSince(long previousEventNanos, String expectedNameHash, - String expectedContentHash) throws Exception { - List<EventLog.Event> events = new ArrayList<>(); - EventLog.readEvents(TAG_LIST, events); - int found = 0; - for (EventLog.Event event : events) { + private static long mostRecentEventTimeNanos() throws Exception { + List<Event> events = readSnetEvents(); + return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos(); + } + + private static void assertDclLoggedSince(long previousEventNanos, String expectedSubTag, + String expectedNameHash, String expectedContentHash) throws Exception { + List<String> messages = + findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash); + + assertWithMessage("Expected exactly one matching log entry").that(messages).hasSize(1); + assertThat(messages.get(0)).endsWith(expectedContentHash); + } + + private static void assertNoDclLoggedSince(long previousEventNanos, String expectedSubTag, + String expectedNameHash) throws Exception { + List<String> messages = + findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash); + + assertWithMessage("Expected no matching log entries").that(messages).isEmpty(); + } + + private static List<String> findMatchingEvents(long previousEventNanos, String expectedSubTag, + String expectedNameHash) throws Exception { + List<String> messages = new ArrayList<>(); + + for (Event event : readSnetEvents()) { if (event.getTimeNanos() <= previousEventNanos) { continue; } - Object[] data = (Object[]) event.getData(); + + Object data = event.getData(); + if (!(data instanceof Object[])) { + continue; + } + Object[] fields = (Object[]) data; // We only care about DCL events that we generated. - String subTag = (String) data[0]; - if (!DCL_SUBTAG.equals(subTag)) { + String subTag = (String) fields[0]; + if (!expectedSubTag.equals(subTag)) { continue; } - int uid = (int) data[1]; + int uid = (int) fields[1]; if (uid != sMyUid) { continue; } - String message = (String) data[2]; + String message = (String) fields[2]; if (!message.startsWith(expectedNameHash)) { continue; } - assertThat(message).endsWith(expectedContentHash); - ++found; + messages.add(message); + //assertThat(message).endsWith(expectedContentHash); } + return messages; + } - assertThat(found).isEqualTo(1); + private static List<Event> readSnetEvents() throws Exception { + List<Event> events = new ArrayList<>(); + EventLog.readEvents(new int[] { SNET_TAG }, events); + return events; } /** diff --git a/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp b/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp new file mode 100644 index 000000000000..060888310b51 --- /dev/null +++ b/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp @@ -0,0 +1,22 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jni.h" + +extern "C" jint JNI_OnLoad(JavaVM* /* vm */, void* /* reserved */) +{ + return JNI_VERSION_1_6; +} diff --git a/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp b/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp new file mode 100644 index 000000000000..ad025e696dec --- /dev/null +++ b/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp @@ -0,0 +1,20 @@ +/* + * 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. + */ + +int main() { + // This program just has to run, it doesn't need to do anything. So we don't. + return 0; +} diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index c2e735e184b0..ec6f4b55d3ea 100644 --- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -38,7 +38,6 @@ import static org.junit.Assert.assertNull; 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; @@ -470,7 +469,7 @@ public class RollbackTest { * Test that app user data is rolled back. * TODO: Stop ignoring this test once user data rollback is supported. */ - @Ignore @Test + @Test public void testUserDataRollback() throws Exception { try { RollbackTestUtils.adoptShellPermissionIdentity( @@ -479,9 +478,9 @@ public class RollbackTest { Manifest.permission.MANAGE_ROLLBACKS); RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppV1.apk", false); + RollbackTestUtils.install("RollbackTestAppAv1.apk", false); processUserData(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppV2.apk", true); + RollbackTestUtils.install("RollbackTestAppAv2.apk", true); processUserData(TEST_APP_A); RollbackManager rm = RollbackTestUtils.getRollbackManager(); diff --git a/tests/net/java/android/net/DnsPacketTest.java b/tests/net/java/android/net/DnsPacketTest.java new file mode 100644 index 000000000000..032e52666970 --- /dev/null +++ b/tests/net/java/android/net/DnsPacketTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DnsPacketTest { + private void assertHeaderParses(DnsPacket.DnsHeader header, int id, int flag, + int qCount, int aCount, int nsCount, int arCount) { + assertEquals(header.id, id); + assertEquals(header.flags, flag); + assertEquals(header.getSectionCount(DnsPacket.QDSECTION), qCount); + assertEquals(header.getSectionCount(DnsPacket.ANSECTION), aCount); + assertEquals(header.getSectionCount(DnsPacket.NSSECTION), nsCount); + assertEquals(header.getSectionCount(DnsPacket.ARSECTION), arCount); + } + + private void assertSectionParses(DnsPacket.DnsSection section, String dname, + int dtype, int dclass, int ttl, byte[] rr) { + assertEquals(section.dName, dname); + assertEquals(section.nsType, dtype); + assertEquals(section.nsClass, dclass); + assertEquals(section.ttl, ttl); + assertTrue(Arrays.equals(section.getRR(), rr)); + } + + class TestDnsPacket extends DnsPacket { + TestDnsPacket(byte[] data) throws ParseException { + super(data); + } + + public DnsHeader getHeader() { + return mHeader; + } + public List<DnsSection> getSectionList(int secType) { + return mSections[secType]; + } + } + + @Test + public void testNullDisallowed() { + try { + new TestDnsPacket(null); + fail("Exception not thrown for null byte array"); + } catch (DnsPacket.ParseException e) { + } + } + + @Test + public void testV4Answer() throws Exception { + final byte[] v4blob = new byte[] { + /* Header */ + 0x55, 0x66, /* Transaction ID */ + (byte) 0x81, (byte) 0x80, /* Flags */ + 0x00, 0x01, /* Questions */ + 0x00, 0x01, /* Answer RRs */ + 0x00, 0x00, /* Authority RRs */ + 0x00, 0x00, /* Additional RRs */ + /* Queries */ + 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + /* Answers */ + (byte) 0xc0, 0x0c, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x01, 0x2b, /* TTL */ + 0x00, 0x04, /* Data length */ + (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */ + }; + TestDnsPacket packet = new TestDnsPacket(v4blob); + + // Header part + assertHeaderParses(packet.getHeader(), 0x5566, 0x8180, 1, 1, 0, 0); + + // Section part + List<DnsPacket.DnsSection> qdSectionList = + packet.getSectionList(DnsPacket.QDSECTION); + assertEquals(qdSectionList.size(), 1); + assertSectionParses(qdSectionList.get(0), "www.google.com", 1, 1, 0, null); + + List<DnsPacket.DnsSection> anSectionList = + packet.getSectionList(DnsPacket.ANSECTION); + assertEquals(anSectionList.size(), 1); + assertSectionParses(anSectionList.get(0), "www.google.com", 1, 1, 0x12b, + new byte[]{ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 }); + } + + @Test + public void testV6Answer() throws Exception { + final byte[] v6blob = new byte[] { + /* Header */ + 0x77, 0x22, /* Transaction ID */ + (byte) 0x81, (byte) 0x80, /* Flags */ + 0x00, 0x01, /* Questions */ + 0x00, 0x01, /* Answer RRs */ + 0x00, 0x00, /* Authority RRs */ + 0x00, 0x00, /* Additional RRs */ + /* Queries */ + 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ + 0x00, 0x1c, /* Type */ + 0x00, 0x01, /* Class */ + /* Answers */ + (byte) 0xc0, 0x0c, /* Name */ + 0x00, 0x1c, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x00, 0x37, /* TTL */ + 0x00, 0x10, /* Data length */ + 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 /* Address */ + }; + TestDnsPacket packet = new TestDnsPacket(v6blob); + + // Header part + assertHeaderParses(packet.getHeader(), 0x7722, 0x8180, 1, 1, 0, 0); + + // Section part + List<DnsPacket.DnsSection> qdSectionList = + packet.getSectionList(DnsPacket.QDSECTION); + assertEquals(qdSectionList.size(), 1); + assertSectionParses(qdSectionList.get(0), "www.google.com", 28, 1, 0, null); + + List<DnsPacket.DnsSection> anSectionList = + packet.getSectionList(DnsPacket.ANSECTION); + assertEquals(anSectionList.size(), 1); + assertSectionParses(anSectionList.get(0), "www.google.com", 28, 1, 0x37, + new byte[]{ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 }); + } +} diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTest.java b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java new file mode 100644 index 000000000000..d0350aff5ef5 --- /dev/null +++ b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.filters; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; + +/** + * JUnit filter to select tests. + * + * <p>This filter selects tests specified by package name, class name, and method name. With this + * filter, the package and the class options of AndroidJUnitRunner can be superseded. Also the + * restriction that prevents using the package and the class options can be mitigated. + * + * <p><b>Select out tests from Java packages:</b> this option supersedes {@code -e package} option. + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.,package2. \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * Note that the ending {@code .} in package name is mandatory. + * + * <p><b>Select out test classes:</b> this option supersedes {@code -e class} option. + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.ClassA,package2.ClassB \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * + * <p><b>Select out test methods from Java classes:</b> + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.ClassA#methodX,package2.ClassB#methodY \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * + * Those options can be used simultaneously. For example + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.,package2.classA,package3.ClassB#methodZ \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * will select out all tests in package1, all tests in classA, and ClassB#methodZ test. + * + * <p>Note that when this option is specified with either {@code -e package} or {@code -e class} + * option, filtering behaves as logically conjunction. Other options, such as {@code -e notPackage}, + * {@code -e notClass}, {@code -e annotation}, and {@code -e notAnnotation}, should work as expected + * with this SelectTest option. + * + * <p>When specified with {@code -e selectTest_verbose true} option, {@link SelectTest} verbosely + * logs to logcat while parsing {@code -e selectTest} option. + */ +public class SelectTest extends Filter { + + private static final String TAG = SelectTest.class.getSimpleName(); + + @VisibleForTesting + static final String OPTION_SELECT_TEST = "selectTest"; + @VisibleForTesting + static final String OPTION_SELECT_TEST_VERBOSE = OPTION_SELECT_TEST + "_verbose"; + + private static final String ARGUMENT_ITEM_SEPARATOR = ","; + private static final String PACKAGE_NAME_SEPARATOR = "."; + private static final String METHOD_SEPARATOR = "#"; + + @Nullable + private final PackageSet mPackageSet; + + /** + * Construct {@link SelectTest} filter from instrumentation arguments in {@link Bundle}. + * + * @param testArgs instrumentation test arguments. + */ + public SelectTest(@NonNull Bundle testArgs) { + mPackageSet = parseSelectTest(testArgs); + } + + @Override + public boolean shouldRun(Description description) { + if (mPackageSet == null) { + // Accept all tests because this filter is disabled. + return true; + } + String testClassName = description.getClassName(); + String testMethodName = description.getMethodName(); + return mPackageSet.accept(testClassName, testMethodName); + } + + @Override + public String describe() { + return OPTION_SELECT_TEST + "=" + mPackageSet; + } + + /** + * Create {@link #OPTION_SELECT_TEST} argument and add it to {@code testArgs}. + * + * <p>This method is intended to be used at constructor of extended {@link Filter} class. + * + * @param testArgs instrumentation test arguments. + * @param selectTests array of class name to be selected to run. + * @return modified instrumentation test arguments. + */ + @NonNull + protected static Bundle addSelectTest( + @NonNull Bundle testArgs, @NonNull String... selectTests) { + if (selectTests.length == 0) { + return testArgs; + } + testArgs.putString(OPTION_SELECT_TEST, join(Arrays.asList(selectTests))); + return testArgs; + } + + /** + * Parse {@code -e selectTest} argument. + * @param testArgs instrumentation test arguments. + * @return {@link PackageSet} that will filter tests. Returns {@code null} when no + * {@code -e selectTest} option is specified, thus this filter gets disabled. + */ + @Nullable + private static PackageSet parseSelectTest(Bundle testArgs) { + final String selectTestArgs = testArgs.getString(OPTION_SELECT_TEST); + if (selectTestArgs == null) { + Log.w(TAG, "Disabled because no " + OPTION_SELECT_TEST + " option specified"); + return null; + } + + final boolean verbose = new Boolean(testArgs.getString(OPTION_SELECT_TEST_VERBOSE)); + final PackageSet packageSet = new PackageSet(verbose); + for (String selectTestArg : selectTestArgs.split(ARGUMENT_ITEM_SEPARATOR)) { + packageSet.add(selectTestArg); + } + return packageSet; + } + + private static String getPackageName(String selectTestArg) { + int endPackagePos = selectTestArg.lastIndexOf(PACKAGE_NAME_SEPARATOR); + return (endPackagePos < 0) ? "" : selectTestArg.substring(0, endPackagePos); + } + + @Nullable + private static String getClassName(String selectTestArg) { + if (selectTestArg.endsWith(PACKAGE_NAME_SEPARATOR)) { + return null; + } + int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR); + return (methodSepPos < 0) ? selectTestArg : selectTestArg.substring(0, methodSepPos); + } + + @Nullable + private static String getMethodName(String selectTestArg) { + int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR); + return (methodSepPos < 0) ? null : selectTestArg.substring(methodSepPos + 1); + } + + /** Package level filter */ + private static class PackageSet { + private final boolean mVerbose; + /** + * Java package name to {@link ClassSet} map. To represent package filtering, a map value + * can be {@code null}. + */ + private final Map<String, ClassSet> mClassSetMap = new LinkedHashMap<>(); + + PackageSet(boolean verbose) { + mVerbose = verbose; + } + + void add(final String selectTestArg) { + final String packageName = getPackageName(selectTestArg); + final String className = getClassName(selectTestArg); + + if (className == null) { + ClassSet classSet = mClassSetMap.put(packageName, null); // package filtering. + if (mVerbose) { + logging("Select package " + selectTestArg, classSet != null, + "; supersede " + classSet); + } + return; + } + + ClassSet classSet = mClassSetMap.get(packageName); + if (classSet == null) { + if (mClassSetMap.containsKey(packageName)) { + if (mVerbose) { + logging("Select package " + packageName + PACKAGE_NAME_SEPARATOR, true, + " ignore " + selectTestArg); + } + return; + } + classSet = new ClassSet(mVerbose); + mClassSetMap.put(packageName, classSet); + } + classSet.add(selectTestArg); + } + + boolean accept(String className, @Nullable String methodName) { + String packageName = getPackageName(className); + if (!mClassSetMap.containsKey(packageName)) { + return false; + } + ClassSet classSet = mClassSetMap.get(packageName); + return classSet == null || classSet.accept(className, methodName); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String packageName : mClassSetMap.keySet()) { + ClassSet classSet = mClassSetMap.get(packageName); + joiner.add(classSet == null + ? packageName + PACKAGE_NAME_SEPARATOR : classSet.toString()); + } + return joiner.toString(); + } + } + + /** Class level filter */ + private static class ClassSet { + private final boolean mVerbose; + /** + * Java class name to set of method names map. To represent class filtering, a map value + * can be {@code null}. + */ + private final Map<String, Set<String>> mMethodSetMap = new LinkedHashMap<>(); + + ClassSet(boolean verbose) { + mVerbose = verbose; + } + + void add(String selectTestArg) { + final String className = getClassName(selectTestArg); + final String methodName = getMethodName(selectTestArg); + + if (methodName == null) { + Set<String> methodSet = mMethodSetMap.put(className, null); // class filtering. + if (mVerbose) { + logging("Select class " + selectTestArg, methodSet != null, + "; supersede " + toString(className, methodSet)); + } + return; + } + + Set<String> methodSet = mMethodSetMap.get(className); + if (methodSet == null) { + if (mMethodSetMap.containsKey(className)) { + if (mVerbose) { + logging("Select class " + className, true, "; ignore " + selectTestArg); + } + return; + } + methodSet = new LinkedHashSet<>(); + mMethodSetMap.put(className, methodSet); + } + + methodSet.add(methodName); + if (mVerbose) { + logging("Select method " + selectTestArg, false, null); + } + } + + boolean accept(String className, @Nullable String methodName) { + if (!mMethodSetMap.containsKey(className)) { + return false; + } + Set<String> methodSet = mMethodSetMap.get(className); + return methodName == null || methodSet == null || methodSet.contains(methodName); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String className : mMethodSetMap.keySet()) { + joiner.add(toString(className, mMethodSetMap.get(className))); + } + return joiner.toString(); + } + + private static String toString(String className, @Nullable Set<String> methodSet) { + if (methodSet == null) { + return className; + } + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String methodName : methodSet) { + joiner.add(className + METHOD_SEPARATOR + methodName); + } + return joiner.toString(); + } + } + + private static void logging(String infoLog, boolean isWarning, String warningLog) { + if (isWarning) { + Log.w(TAG, infoLog + warningLog); + } else { + Log.i(TAG, infoLog); + } + } + + private static String join(Collection<String> list) { + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String text : list) { + joiner.add(text); + } + return joiner.toString(); + } +} diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java new file mode 100644 index 000000000000..a6b0102a511f --- /dev/null +++ b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.filters; + +import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST; +import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST_VERBOSE; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.Bundle; +import android.util.ArraySet; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringJoiner; + +public class SelectTestTests { + + private static final String PACKAGE_A = "packageA."; + private static final String PACKAGE_B = "packageB."; + private static final String PACKAGE_C = "packageC."; + private static final String CLASS_A1 = PACKAGE_A + "Class1"; + private static final String CLASS_A2 = PACKAGE_A + "Class2"; + private static final String CLASS_B3 = PACKAGE_B + "Class3"; + private static final String CLASS_B4 = PACKAGE_B + "Class4"; + private static final String CLASS_C5 = PACKAGE_C + "Class5"; + private static final String CLASS_C6 = PACKAGE_C + "Class6"; + private static final String METHOD_A1K = CLASS_A1 + "#methodK"; + private static final String METHOD_A1L = CLASS_A1 + "#methodL"; + private static final String METHOD_A2M = CLASS_A2 + "#methodM"; + private static final String METHOD_A2N = CLASS_A2 + "#methodN"; + private static final String METHOD_B3P = CLASS_B3 + "#methodP"; + private static final String METHOD_B3Q = CLASS_B3 + "#methodQ"; + private static final String METHOD_B4R = CLASS_B4 + "#methodR"; + private static final String METHOD_B4S = CLASS_B4 + "#methodS"; + private static final String METHOD_C5W = CLASS_C5 + "#methodW"; + private static final String METHOD_C5X = CLASS_C5 + "#methodX"; + private static final String METHOD_C6Y = CLASS_C6 + "#methodY"; + private static final String METHOD_C6Z = CLASS_C6 + "#methodZ"; + + private static final Set<Description> TEST_METHOD_A1K = methodTest(METHOD_A1K); + private static final Set<Description> TEST_METHOD_A1L = methodTest(METHOD_A1L); + private static final Set<Description> TEST_METHOD_A2M = methodTest(METHOD_A2M); + private static final Set<Description> TEST_METHOD_A2N = methodTest(METHOD_A2N); + private static final Set<Description> TEST_METHOD_B3P = methodTest(METHOD_B3P); + private static final Set<Description> TEST_METHOD_B3Q = methodTest(METHOD_B3Q); + private static final Set<Description> TEST_METHOD_B4R = methodTest(METHOD_B4R); + private static final Set<Description> TEST_METHOD_B4S = methodTest(METHOD_B4S); + private static final Set<Description> TEST_METHOD_C5W = methodTest(METHOD_C5W); + private static final Set<Description> TEST_METHOD_C5X = methodTest(METHOD_C5X); + private static final Set<Description> TEST_METHOD_C6Y = methodTest(METHOD_C6Y); + private static final Set<Description> TEST_METHOD_C6Z = methodTest(METHOD_C6Z); + private static final Set<Description> TEST_CLASS_A1 = merge(TEST_METHOD_A1K, TEST_METHOD_A1L); + private static final Set<Description> TEST_CLASS_A2 = merge(TEST_METHOD_A2M, TEST_METHOD_A2N); + private static final Set<Description> TEST_CLASS_B3 = merge(TEST_METHOD_B3P, TEST_METHOD_B3Q); + private static final Set<Description> TEST_CLASS_B4 = merge(TEST_METHOD_B4R, TEST_METHOD_B4S); + private static final Set<Description> TEST_CLASS_C5 = merge(TEST_METHOD_C5W, TEST_METHOD_C5X); + private static final Set<Description> TEST_CLASS_C6 = merge(TEST_METHOD_C6Y, TEST_METHOD_C6Z); + private static final Set<Description> TEST_PACKAGE_A = merge(TEST_CLASS_A1, TEST_CLASS_A2); + private static final Set<Description> TEST_PACKAGE_B = merge(TEST_CLASS_B3, TEST_CLASS_B4); + private static final Set<Description> TEST_PACKAGE_C = merge(TEST_CLASS_C5, TEST_CLASS_C6); + private static final Set<Description> TEST_ALL = + merge(TEST_PACKAGE_A, TEST_PACKAGE_B, TEST_PACKAGE_C); + + private SelectTestBuilder mBuilder; + + @Before + public void setUp() { + mBuilder = new SelectTestBuilder(); + } + + private static class SelectTestBuilder { + private final Bundle mTestArgs = new Bundle(); + + Filter build() { + mTestArgs.putString(OPTION_SELECT_TEST_VERBOSE, Boolean.TRUE.toString()); + return new SelectTest(mTestArgs); + } + + SelectTestBuilder withSelectTest(String... selectTestArgs) { + putTestOption(OPTION_SELECT_TEST, selectTestArgs); + return this; + } + + private void putTestOption(String option, String... args) { + if (args.length > 0) { + StringJoiner joiner = new StringJoiner(","); + for (String arg : args) { + joiner.add(arg); + } + mTestArgs.putString(option, joiner.toString()); + } + } + } + + private static Set<Description> methodTest(String testName) { + int methodSep = testName.indexOf("#"); + String className = testName.substring(0, methodSep); + String methodName = testName.substring(methodSep + 1); + final Set<Description> tests = new ArraySet<>(); + tests.add(Description.createSuiteDescription(className)); + tests.add(Description.createTestDescription(className, methodName)); + return Collections.unmodifiableSet(tests); + } + + @SafeVarargs + private static Set<Description> merge(Set<Description>... testSpecs) { + final Set<Description> merged = new LinkedHashSet<>(); + for (Set<Description> testSet : testSpecs) { + merged.addAll(testSet); + } + return Collections.unmodifiableSet(merged); + } + + @SafeVarargs + private static void acceptTests(Filter filter, Set<Description>... testSpecs) { + final Set<Description> accepts = merge(testSpecs); + for (Description test : TEST_ALL) { + if (accepts.contains(test)) { + assertTrue("accept " + test, filter.shouldRun(test)); + } else { + assertFalse("reject " + test, filter.shouldRun(test)); + } + } + } + + @Test + public void testFilterDisabled() { + final Filter filter = mBuilder.build(); + acceptTests(filter, TEST_ALL); + } + + @Test + public void testSelectPackage() { + final Filter filter = mBuilder.withSelectTest(PACKAGE_A, PACKAGE_B).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B); + } + + @Test + public void testSelectClass() { + final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, CLASS_B3).build(); + acceptTests(filter, TEST_CLASS_A1, TEST_CLASS_A2, TEST_CLASS_B3); + } + + @Test + public void testSelectMethod() { + final Filter filter = mBuilder + .withSelectTest(METHOD_A1K, METHOD_A2M, METHOD_A2N, METHOD_B3P).build(); + acceptTests(filter, TEST_METHOD_A1K, TEST_METHOD_A2M, TEST_METHOD_A2N, TEST_METHOD_B3P); + } + + @Test + public void testSelectClassAndPackage() { + final Filter filter = mBuilder.withSelectTest(CLASS_A1, PACKAGE_B, CLASS_C5).build(); + acceptTests(filter, TEST_CLASS_A1, TEST_PACKAGE_B, TEST_CLASS_C5); + } + + @Test + public void testSelectMethodAndPackage() { + final Filter filter = mBuilder.withSelectTest(METHOD_A1K, PACKAGE_B, METHOD_C5W).build(); + acceptTests(filter, TEST_METHOD_A1K, TEST_PACKAGE_B, TEST_METHOD_C5W); + } + + @Test + public void testSelectMethodAndClass() { + final Filter filter = mBuilder.withSelectTest(METHOD_A1K, CLASS_C5, METHOD_B3P).build(); + acceptTests(filter, TEST_METHOD_A1K, TEST_CLASS_C5, TEST_METHOD_B3P); + } + + @Test + public void testSelectClassAndSamePackage() { + final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, PACKAGE_A, + CLASS_C5, CLASS_C6, PACKAGE_C).build(); + acceptTests(filter, TEST_PACKAGE_A, 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(); + acceptTests(filter, TEST_CLASS_A1, TEST_METHOD_A2M, TEST_CLASS_B3, TEST_METHOD_B4R); + } + + @Test + public void testSelectMethodAndSamePackage() { + final Filter filter = mBuilder.withSelectTest(METHOD_A1K, METHOD_A1L, METHOD_A2M, PACKAGE_A, + PACKAGE_C, METHOD_C5W, METHOD_C5X, METHOD_C6Y).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C); + } + + @Test + public void testSelectMethodAndClassAndPackage() { + final Filter filter = mBuilder.withSelectTest( + METHOD_A1K, CLASS_A1, METHOD_A1L, METHOD_A2M, PACKAGE_A, + PACKAGE_B, METHOD_B3Q, CLASS_B3, METHOD_B4R, METHOD_B3P).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B); + } +} diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 92beb4eb7ce4..0512bdc5bf72 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -469,16 +469,12 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, return false; } - // Read the file as a string - char buffer_2[data->size()]; - memcpy(&buffer_2, data->data(), data->size()); - StringPiece content(buffer_2, data->size()); - BigBuffer crunched_png_buffer(4096); io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer); // Ensure that we only keep the chunks we care about if we end up // using the original PNG instead of the crunched one. + const StringPiece content(reinterpret_cast<const char*>(data->data()), data->size()); PngChunkFilter png_chunk_filter(content); std::unique_ptr<Image> image = ReadPng(context, path_data.source, &png_chunk_filter); if (!image) { |