diff options
101 files changed, 3719 insertions, 865 deletions
diff --git a/Android.bp b/Android.bp index ba9156efe4af..47ad24a01924 100644 --- a/Android.bp +++ b/Android.bp @@ -515,11 +515,14 @@ java_defaults { "telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl", "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl", "telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl", + "telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl", "telephony/java/android/telephony/mbms/IDownloadStatusListener.aidl", "telephony/java/android/telephony/mbms/IDownloadProgressListener.aidl", "telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl", + "telephony/java/android/telephony/mbms/IGroupCallCallback.aidl", "telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl", "telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl", + "telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl", "telephony/java/android/telephony/INetworkService.aidl", "telephony/java/android/telephony/INetworkServiceCallback.aidl", "telephony/java/com/android/ims/internal/IImsCallSession.aidl", @@ -807,7 +810,7 @@ gensrcs { java_library { name: "ext", installable: true, - no_framework_libs: true, + sdk_version: "core_current", static_libs: [ "libphonenumber-platform", "nist-sip", diff --git a/api/current.txt b/api/current.txt index bb3cb0b2f5f4..52257c0f7529 100755 --- a/api/current.txt +++ b/api/current.txt @@ -14007,6 +14007,10 @@ package android.graphics { method public float getRunAdvance(char[], int, int, int, int, boolean, int); method public float getRunAdvance(java.lang.CharSequence, int, int, int, int, boolean, int); method public android.graphics.Shader getShader(); + method public int getShadowLayerColor(); + method public float getShadowLayerDx(); + method public float getShadowLayerDy(); + method public float getShadowLayerRadius(); method public float getStrikeThruPosition(); method public float getStrikeThruThickness(); method public android.graphics.Paint.Cap getStrokeCap(); @@ -42266,6 +42270,7 @@ package android.telephony { field public static final deprecated java.lang.String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool"; field public static final java.lang.String KEY_RTT_SUPPORTED_BOOL = "rtt_supported_bool"; field public static final java.lang.String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool"; + field public static final java.lang.String KEY_SHOW_CALL_BLOCKING_DISABLED_NOTIFICATION_ALWAYS_BOOL = "show_call_blocking_disabled_notification_always_bool"; field public static final java.lang.String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool"; field public static final java.lang.String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool"; field public static final java.lang.String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool"; @@ -42527,6 +42532,13 @@ package android.telephony { field public static final int STATUS_UNKNOWN = 0; // 0x0 } + public class MbmsGroupCallSession implements java.lang.AutoCloseable { + method public void close(); + method public static android.telephony.MbmsGroupCallSession create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsGroupCallSessionCallback); + method public static android.telephony.MbmsGroupCallSession create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsGroupCallSessionCallback); + method public android.telephony.mbms.GroupCall startGroupCall(java.util.concurrent.Executor, long, int[], int[], android.telephony.mbms.GroupCallCallback); + } + public class MbmsStreamingSession implements java.lang.AutoCloseable { method public void close(); method public static android.telephony.MbmsStreamingSession create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsStreamingSessionCallback); @@ -43476,6 +43488,29 @@ package android.telephony.mbms { field public static final android.os.Parcelable.Creator<android.telephony.mbms.FileServiceInfo> CREATOR; } + public class GroupCall implements java.lang.AutoCloseable { + method public void close(); + method public long getTmgi(); + method public void updateGroupCall(int[], int[]); + field public static final int REASON_BY_USER_REQUEST = 1; // 0x1 + field public static final int REASON_FREQUENCY_CONFLICT = 3; // 0x3 + field public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 6; // 0x6 + field public static final int REASON_NONE = 0; // 0x0 + field public static final int REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE = 5; // 0x5 + field public static final int REASON_OUT_OF_MEMORY = 4; // 0x4 + field public static final int STATE_STALLED = 3; // 0x3 + field public static final int STATE_STARTED = 2; // 0x2 + field public static final int STATE_STOPPED = 1; // 0x1 + } + + public class GroupCallCallback { + ctor public GroupCallCallback(); + method public void onBroadcastSignalStrengthUpdated(int); + method public void onError(int, java.lang.String); + method public void onGroupCallStateChanged(int, int); + field public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1; // 0xffffffff + } + public class MbmsDownloadReceiver extends android.content.BroadcastReceiver { ctor public MbmsDownloadReceiver(); method public void onReceive(android.content.Context, android.content.Intent); @@ -43524,6 +43559,14 @@ package android.telephony.mbms { field public static final int ERROR_UNABLE_TO_START_SERVICE = 302; // 0x12e } + public class MbmsGroupCallSessionCallback { + ctor public MbmsGroupCallSessionCallback(); + method public void onAvailableSaisUpdated(java.util.List<java.lang.Integer>, java.util.List<java.util.List<java.lang.Integer>>); + method public void onError(int, java.lang.String); + method public void onMiddlewareReady(); + method public void onServiceInterfaceAvailable(java.lang.String, int); + } + public class MbmsStreamingSessionCallback { ctor public MbmsStreamingSessionCallback(); method public void onError(int, java.lang.String); @@ -44158,6 +44201,8 @@ package android.text { field public float density; field public int[] drawableState; field public int linkColor; + field public int underlineColor; + field public float underlineThickness; } public class TextUtils { diff --git a/api/system-current.txt b/api/system-current.txt index 1009b672cf65..1f74cbf6312c 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -950,6 +950,7 @@ package android.content { field public static final java.lang.String HDMI_CONTROL_SERVICE = "hdmi_control"; field public static final java.lang.String NETWORK_SCORE_SERVICE = "network_score"; field public static final java.lang.String OEM_LOCK_SERVICE = "oem_lock"; + field public static final java.lang.String PERMISSION_SERVICE = "permission"; field public static final java.lang.String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block"; field public static final java.lang.String SECURE_ELEMENT_SERVICE = "secure_element"; field public static final java.lang.String STATS_MANAGER = "stats"; @@ -4192,6 +4193,20 @@ package android.os.storage { } +package android.permission { + + public final class PermissionManager { + method public java.util.List<android.permission.PermissionManager.SplitPermissionInfo> getSplitPermissions(); + } + + public static final class PermissionManager.SplitPermissionInfo { + method public java.lang.String[] getNewPermissions(); + method public java.lang.String getRootPermission(); + method public int getTargetSdk(); + } + +} + package android.permissionpresenterservice { public abstract class RuntimePermissionPresenterService extends android.app.Service { @@ -5257,6 +5272,10 @@ package android.telephony { field public static final java.lang.String MBMS_DOWNLOAD_SERVICE_ACTION = "android.telephony.action.EmbmsDownload"; } + public class MbmsGroupCallSession implements java.lang.AutoCloseable { + field public static final java.lang.String MBMS_GROUP_CALL_SERVICE_ACTION = "android.telephony.action.EmbmsGroupCall"; + } + public class MbmsStreamingSession implements java.lang.AutoCloseable { field public static final java.lang.String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming"; } @@ -5434,6 +5453,7 @@ package android.telephony { method public deprecated boolean getDataEnabled(); method public deprecated boolean getDataEnabled(int); method public boolean getEmergencyCallbackMode(); + method public java.lang.String getIsimDomain(); method public int getSimApplicationState(); method public int getSimCardState(); method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms(); @@ -6558,6 +6578,17 @@ package android.telephony.mbms.vendor { method public int setTempFileRootDirectory(int, java.lang.String) throws android.os.RemoteException; } + public class MbmsGroupCallServiceBase extends android.app.Service { + ctor public MbmsGroupCallServiceBase(); + method public void dispose(int) throws android.os.RemoteException; + method public int initialize(android.telephony.mbms.MbmsGroupCallSessionCallback, int) throws android.os.RemoteException; + method public void onAppCallbackDied(int, int); + method public android.os.IBinder onBind(android.content.Intent); + method public int startGroupCall(int, long, int[], int[], android.telephony.mbms.GroupCallCallback); + method public void stopGroupCall(int, long); + method public void updateGroupCall(int, long, int[], int[]); + } + public class MbmsStreamingServiceBase extends android.os.Binder { ctor public MbmsStreamingServiceBase(); method public void dispose(int) throws android.os.RemoteException; diff --git a/api/test-current.txt b/api/test-current.txt index f4d7cbcab6dc..956761615254 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1188,6 +1188,10 @@ package android.telephony { field public static final java.lang.String MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA = "mbms-download-service-override"; } + public class MbmsGroupCallSession implements java.lang.AutoCloseable { + field public static final java.lang.String MBMS_GROUP_CALL_SERVICE_OVERRIDE_METADATA = "mbms-group-call-service-override"; + } + public class MbmsStreamingSession implements java.lang.AutoCloseable { field public static final java.lang.String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA = "mbms-streaming-service-override"; } @@ -1257,6 +1261,17 @@ package android.telephony.mbms.vendor { method public int setTempFileRootDirectory(int, java.lang.String) throws android.os.RemoteException; } + public class MbmsGroupCallServiceBase extends android.app.Service { + ctor public MbmsGroupCallServiceBase(); + method public void dispose(int) throws android.os.RemoteException; + method public int initialize(android.telephony.mbms.MbmsGroupCallSessionCallback, int) throws android.os.RemoteException; + method public void onAppCallbackDied(int, int); + method public android.os.IBinder onBind(android.content.Intent); + method public int startGroupCall(int, long, int[], int[], android.telephony.mbms.GroupCallCallback); + method public void stopGroupCall(int, long); + method public void updateGroupCall(int, long, int[], int[]); + } + public class MbmsStreamingServiceBase extends android.os.Binder { ctor public MbmsStreamingServiceBase(); method public void dispose(int) throws android.os.RemoteException; diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 485b91fb8ece..448608e9ece7 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -136,6 +136,7 @@ message Atom { FingerprintAcquired fingerprint_acquired = 87; FingerprintAuthenticated fingerprint_authenticated = 88; FingerprintErrorOccurred fingerprint_error_occurred = 89; + Notification notification = 90; } // Pulled events will start at field 10000. @@ -1891,6 +1892,69 @@ message FingerprintErrorOccurred { // The type of error. optional Error error = 3; } + +message Notification { + + // Type of notification event. + enum Type { + TYPE_UNKNOWN = 0; + // Notification became visible to the user. + TYPE_OPEN = 1; + // Notification became hidden. + TYPE_CLOSE = 2; + // Notification switched to detail mode. + TYPE_DETAIL = 3; + // Notification was clicked. + TYPE_ACTION = 4; + // Notification was dismissed. + TYPE_DISMISS = 5; + // Notification switched to summary mode. The enum value of 14 is to + // match that of metrics_constants. + TYPE_COLLAPSE = 14; + } + optional Type type = 1; + + // Package name associated with the notification. + optional string package_name = 2; + + // Tag associated with notification. + optional string tag = 3; + + // Application-supplied ID associated with the notification. + optional int32 id = 4; + + // Index of notification in the notification panel. + optional int32 shade_index = 5; + + // The number of notifications in the notification panel. + optional int32 shade_count = 6; + + // Importance for the notification. + optional int32 importance = 7; + + // ID for the notification channel. + optional int32 channel_id = 8; + + // Importance for the notification channel. + optional int32 channel_importance = 9; + + // Whether notification was a group summary. + optional bool group_summary = 10; + + // Time since notification was created in milliseconds. + optional int64 since_create_millis = 11; + + // Time since notification was interrupted in milliseconds. + optional int64 since_interruption_millis = 12; + + // Time since notification was updated in milliseconds. + optional int64 since_update_millis = 13; + + // Time since notification was visible in milliseconds. + optional int64 since_visible_millis = 14; +} + + ////////////////////////////////////////////////////////////////////// // Pulled atoms below this line // ////////////////////////////////////////////////////////////////////// @@ -2156,6 +2220,11 @@ message ProcessMemoryState { // SWAP optional int64 swap_in_bytes = 8; + + // RSS high watermark. + // Peak RSS usage of the process. Value is read from the VmHWM field in /proc/PID/status or + // from memory.max_usage_in_bytes under /dev/memcg if the device uses per-app memory cgroups. + optional int64 rss_high_watermark_in_bytes = 9; } /* diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 5a0172b22301..66392f80f1fe 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -169,7 +169,7 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}}, // process_memory_state {android::util::PROCESS_MEMORY_STATE, - {{4, 5, 6, 7, 8}, + {{4, 5, 6, 7, 8, 9}, {2, 3}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}}, diff --git a/core/java/android/app/ProcessMemoryState.java b/core/java/android/app/ProcessMemoryState.java index e0aed16b8abf..9bfdae0d8c03 100644 --- a/core/java/android/app/ProcessMemoryState.java +++ b/core/java/android/app/ProcessMemoryState.java @@ -32,10 +32,11 @@ public final class ProcessMemoryState implements Parcelable { public final long rssInBytes; public final long cacheInBytes; public final long swapInBytes; + public final long rssHighWatermarkInBytes; public ProcessMemoryState(int uid, String processName, int oomScore, long pgfault, long pgmajfault, long rssInBytes, long cacheInBytes, - long swapInBytes) { + long swapInBytes, long rssHighWatermarkInBytes) { this.uid = uid; this.processName = processName; this.oomScore = oomScore; @@ -44,6 +45,7 @@ public final class ProcessMemoryState implements Parcelable { this.rssInBytes = rssInBytes; this.cacheInBytes = cacheInBytes; this.swapInBytes = swapInBytes; + this.rssHighWatermarkInBytes = rssHighWatermarkInBytes; } private ProcessMemoryState(Parcel in) { @@ -55,6 +57,7 @@ public final class ProcessMemoryState implements Parcelable { rssInBytes = in.readLong(); cacheInBytes = in.readLong(); swapInBytes = in.readLong(); + rssHighWatermarkInBytes = in.readLong(); } public static final Creator<ProcessMemoryState> CREATOR = new Creator<ProcessMemoryState>() { @@ -84,5 +87,6 @@ public final class ProcessMemoryState implements Parcelable { parcel.writeLong(rssInBytes); parcel.writeLong(cacheInBytes); parcel.writeLong(swapInBytes); + parcel.writeLong(rssHighWatermarkInBytes); } } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 41dec8f10937..0044005c51f2 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -134,6 +134,7 @@ import android.os.UserManager; import android.os.Vibrator; import android.os.health.SystemHealthManager; import android.os.storage.StorageManager; +import android.permission.PermissionManager; import android.print.IPrintManager; import android.print.PrintManager; import android.service.oemlock.IOemLockService; @@ -1064,6 +1065,13 @@ final class SystemServiceRegistry { throws ServiceNotFoundException { return new TimeZoneDetector(); }}); + + registerService(Context.PERMISSION_SERVICE, PermissionManager.class, + new CachedServiceFetcher<PermissionManager>() { + @Override + public PermissionManager createService(ContextImpl ctx) { + return new PermissionManager(ctx.getOuterContext()); + }}); } /** diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java index 183be5f38bd1..559a59b68b4e 100644 --- a/core/java/android/bluetooth/BluetoothMapClient.java +++ b/core/java/android/bluetooth/BluetoothMapClient.java @@ -73,6 +73,8 @@ public final class BluetoothMapClient implements BluetoothProfile { /** Connection canceled before completion. */ public static final int RESULT_CANCELED = 2; + private static final int UPLOADING_FEATURE_BITMASK = 0x08; + private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = new IBluetoothStateChangeCallback.Stub() { public void onBluetoothStateChange(boolean up) { @@ -395,6 +397,23 @@ public final class BluetoothMapClient implements BluetoothProfile { return false; } + /** + * Returns the "Uploading" feature bit value from the SDP record's + * MapSupportedFeatures field (see Bluetooth MAP 1.4 spec, page 114). + * @param device The Bluetooth device to get this value for. + * @return Returns true if the Uploading bit value in SDP record's + * MapSupportedFeatures field is set. False is returned otherwise. + */ + public boolean isUploadingSupported(BluetoothDevice device) { + try { + return (mService != null && isEnabled() && isValidDevice(device)) + && ((mService.getSupportedFeatures(device) & UPLOADING_FEATURE_BITMASK) > 0); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + return false; + } + private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index bd1e6a462805..8d9533e09cbf 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -51,6 +51,8 @@ import android.os.storage.StorageManager; import android.text.TextUtils; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; @@ -58,6 +60,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Objects; /** * Content providers are one of the primary building blocks of Android applications, providing @@ -99,6 +102,7 @@ import java.util.Arrays; * <p>For more information about using content providers, read the * <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> * developer guide.</p> + * </div> */ public abstract class ContentProvider implements ComponentCallbacks2 { @@ -220,7 +224,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { @Override public Cursor query(String callingPkg, Uri uri, @Nullable String[] projection, @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) { - validateIncomingUri(uri); + uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { // The caller has no access to the data, so return an empty cursor with @@ -268,7 +272,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { @Override public String getType(Uri uri) { // getCallingPackage() isn't available in getType(), as the javadoc states. - validateIncomingUri(uri); + uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); Trace.traceBegin(TRACE_TAG_DATABASE, "getType"); try { @@ -280,7 +284,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { @Override public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) { - validateIncomingUri(uri); + uri = validateIncomingUri(uri); int userId = getUserIdFromUri(uri); uri = maybeGetUriWithoutUserId(uri); if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { @@ -303,7 +307,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { @Override public int bulkInsert(String callingPkg, Uri uri, ContentValues[] initialValues) { - validateIncomingUri(uri); + uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { return 0; @@ -327,11 +331,11 @@ public abstract class ContentProvider implements ComponentCallbacks2 { for (int i = 0; i < numOperations; i++) { ContentProviderOperation operation = operations.get(i); Uri uri = operation.getUri(); - validateIncomingUri(uri); - userIds[i] = getUserIdFromUri(uri); - if (userIds[i] != UserHandle.USER_CURRENT) { - // Removing the user id from the uri. - operation = new ContentProviderOperation(operation, true); + uri = validateIncomingUri(uri); + uri = maybeGetUriWithoutUserId(uri); + // Rebuild operation if we changed the Uri above + if (!Objects.equals(operation.getUri(), uri)) { + operation = new ContentProviderOperation(operation, uri); operations.set(i, operation); } if (operation.isReadOperation()) { @@ -368,7 +372,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { @Override public int delete(String callingPkg, Uri uri, String selection, String[] selectionArgs) { - validateIncomingUri(uri); + uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { return 0; @@ -386,7 +390,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { @Override public int update(String callingPkg, Uri uri, ContentValues values, String selection, String[] selectionArgs) { - validateIncomingUri(uri); + uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { return 0; @@ -405,7 +409,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { public ParcelFileDescriptor openFile( String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal, IBinder callerToken) throws FileNotFoundException { - validateIncomingUri(uri); + uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); enforceFilePermission(callingPkg, uri, mode, callerToken); Trace.traceBegin(TRACE_TAG_DATABASE, "openFile"); @@ -423,7 +427,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { public AssetFileDescriptor openAssetFile( String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal) throws FileNotFoundException { - validateIncomingUri(uri); + uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); enforceFilePermission(callingPkg, uri, mode, null); Trace.traceBegin(TRACE_TAG_DATABASE, "openAssetFile"); @@ -454,7 +458,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { @Override public String[] getStreamTypes(Uri uri, String mimeTypeFilter) { // getCallingPackage() isn't available in getType(), as the javadoc states. - validateIncomingUri(uri); + uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); Trace.traceBegin(TRACE_TAG_DATABASE, "getStreamTypes"); try { @@ -468,7 +472,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType, Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException { Bundle.setDefusable(opts, true); - validateIncomingUri(uri); + uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); enforceFilePermission(callingPkg, uri, "r", null); Trace.traceBegin(TRACE_TAG_DATABASE, "openTypedAssetFile"); @@ -489,7 +493,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { @Override public Uri canonicalize(String callingPkg, Uri uri) { - validateIncomingUri(uri); + uri = validateIncomingUri(uri); int userId = getUserIdFromUri(uri); uri = getUriWithoutUserId(uri); if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { @@ -507,7 +511,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { @Override public Uri uncanonicalize(String callingPkg, Uri uri) { - validateIncomingUri(uri); + uri = validateIncomingUri(uri); int userId = getUserIdFromUri(uri); uri = getUriWithoutUserId(uri); if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { @@ -526,7 +530,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { @Override public boolean refresh(String callingPkg, Uri uri, Bundle args, ICancellationSignal cancellationSignal) throws RemoteException { - validateIncomingUri(uri); + uri = validateIncomingUri(uri); uri = getUriWithoutUserId(uri); if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { return false; @@ -1965,7 +1969,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { */ if (mContext == null) { mContext = context; - if (context != null) { + if (context != null && mTransport != null) { mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService( Context.APP_OPS_SERVICE); } @@ -2074,7 +2078,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } /** @hide */ - private void validateIncomingUri(Uri uri) throws SecurityException { + @VisibleForTesting + public Uri validateIncomingUri(Uri uri) throws SecurityException { String auth = uri.getAuthority(); if (!mSingleUser) { int userId = getUserIdFromAuthority(auth, UserHandle.USER_CURRENT); @@ -2093,6 +2098,15 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } throw new SecurityException(message); } + + // Normalize the path by removing any empty path segments, which can be + // a source of security issues. + final String encodedPath = uri.getEncodedPath(); + if (encodedPath != null && encodedPath.indexOf("//") != -1) { + return uri.buildUpon().encodedPath(encodedPath.replaceAll("//+", "/")).build(); + } else { + return uri; + } } /** @hide */ diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java index e3d9b1931faa..7dc45776715c 100644 --- a/core/java/android/content/ContentProviderOperation.java +++ b/core/java/android/content/ContentProviderOperation.java @@ -101,13 +101,9 @@ public class ContentProviderOperation implements Parcelable { } /** @hide */ - public ContentProviderOperation(ContentProviderOperation cpo, boolean removeUserIdFromUri) { + public ContentProviderOperation(ContentProviderOperation cpo, Uri withUri) { mType = cpo.mType; - if (removeUserIdFromUri) { - mUri = ContentProvider.getUriWithoutUserId(cpo.mUri); - } else { - mUri = cpo.mUri; - } + mUri = withUri; mValues = cpo.mValues; mSelection = cpo.mSelection; mSelectionArgs = cpo.mSelectionArgs; @@ -117,14 +113,6 @@ public class ContentProviderOperation implements Parcelable { mYieldAllowed = cpo.mYieldAllowed; } - /** @hide */ - public ContentProviderOperation getWithoutUserIdInUri() { - if (ContentProvider.uriHasUserId(mUri)) { - return new ContentProviderOperation(this, true); - } - return this; - } - public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); Uri.writeToParcel(dest, mUri); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 981be833f3c0..caaf4af247e9 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3088,6 +3088,7 @@ public abstract class Context { //@hide: SYSTEM_UPDATE_SERVICE, //@hide: TIME_DETECTOR_SERVICE, //@hide: TIME_ZONE_DETECTOR_SERVICE, + PERMISSION_SERVICE, }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -3860,6 +3861,14 @@ public abstract class Context { */ public static final String SOUND_TRIGGER_SERVICE = "soundtrigger"; + /** + * Official published name of the (internal) permission service. + * + * @see #getSystemService(String) + * @hide + */ + @SystemApi + public static final String PERMISSION_SERVICE = "permission"; /** * Use with {@link #getSystemService(String)} to retrieve an diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 03a3d1f438ff..1fa5190ef8df 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -40,7 +40,6 @@ import static android.os.Build.VERSION_CODES.O; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; -import android.Manifest; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -73,6 +72,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.storage.StorageManager; +import android.permission.PermissionManager; import android.system.ErrnoException; import android.system.OsConstants; import android.system.StructStat; @@ -258,19 +258,6 @@ public class PackageParser { } } - /** @hide */ - public static class SplitPermissionInfo { - public final String rootPerm; - public final String[] newPerms; - public final int targetSdk; - - public SplitPermissionInfo(String rootPerm, String[] newPerms, int targetSdk) { - this.rootPerm = rootPerm; - this.newPerms = newPerms; - this.targetSdk = targetSdk; - } - } - /** * List of new permissions that have been added since 1.0. * NOTE: These must be declared in SDK version order, with permissions @@ -290,34 +277,6 @@ public class PackageParser { }; /** - * List of permissions that have been split into more granular or dependent - * permissions. - * @hide - */ - public static final PackageParser.SplitPermissionInfo SPLIT_PERMISSIONS[] = - new PackageParser.SplitPermissionInfo[] { - // READ_EXTERNAL_STORAGE is always required when an app requests - // WRITE_EXTERNAL_STORAGE, because we can't have an app that has - // write access without read access. The hack here with the target - // target SDK version ensures that this grant is always done. - new PackageParser.SplitPermissionInfo(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, - new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE }, - android.os.Build.VERSION_CODES.CUR_DEVELOPMENT+1), - new PackageParser.SplitPermissionInfo(android.Manifest.permission.READ_CONTACTS, - new String[] { android.Manifest.permission.READ_CALL_LOG }, - android.os.Build.VERSION_CODES.JELLY_BEAN), - new PackageParser.SplitPermissionInfo(android.Manifest.permission.WRITE_CONTACTS, - new String[] { android.Manifest.permission.WRITE_CALL_LOG }, - android.os.Build.VERSION_CODES.JELLY_BEAN), - new PackageParser.SplitPermissionInfo(Manifest.permission.ACCESS_FINE_LOCATION, - new String[] { android.Manifest.permission.ACCESS_BACKGROUND_LOCATION }, - android.os.Build.VERSION_CODES.P0), - new PackageParser.SplitPermissionInfo(Manifest.permission.ACCESS_COARSE_LOCATION, - new String[] { android.Manifest.permission.ACCESS_BACKGROUND_LOCATION }, - android.os.Build.VERSION_CODES.P0), - }; - - /** * @deprecated callers should move to explicitly passing around source path. */ @Deprecated @@ -2474,16 +2433,18 @@ public class PackageParser { Slog.i(TAG, implicitPerms.toString()); } - final int NS = PackageParser.SPLIT_PERMISSIONS.length; + + final int NS = PermissionManager.SPLIT_PERMISSIONS.length; for (int is=0; is<NS; is++) { - final PackageParser.SplitPermissionInfo spi - = PackageParser.SPLIT_PERMISSIONS[is]; - if (pkg.applicationInfo.targetSdkVersion >= spi.targetSdk - || !pkg.requestedPermissions.contains(spi.rootPerm)) { + final PermissionManager.SplitPermissionInfo spi = + PermissionManager.SPLIT_PERMISSIONS[is]; + if (pkg.applicationInfo.targetSdkVersion >= spi.getTargetSdk() + || !pkg.requestedPermissions.contains(spi.getRootPermission())) { continue; } - for (int in=0; in<spi.newPerms.length; in++) { - final String perm = spi.newPerms[in]; + final String[] newPerms = spi.getNewPermissions(); + for (int in = 0; in < newPerms.length; in++) { + final String perm = newPerms[in]; if (!pkg.requestedPermissions.contains(perm)) { pkg.requestedPermissions.add(perm); } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index b94840279cea..ae12f93285a8 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -410,14 +410,6 @@ public class InputMethodService extends AbstractInputMethodService { @GuardedBy("mLock") private boolean mNotifyUserActionSent; - /** - * {@code true} when the previous IME had non-empty inset at the bottom of the screen and we - * have not shown our own window yet. In this situation, the previous inset continues to be - * shown as an empty region until it is explicitly updated. Basically we can trigger the update - * by calling 1) {@code mWindow.show()} or 2) {@link #clearInsetOfPreviousIme()}. - */ - boolean mShouldClearInsetOfPreviousIme; - @UnsupportedAppUsage final Insets mTmpInsets = new Insets(); final int[] mTmpLocation = new int[2]; @@ -581,7 +573,6 @@ public class InputMethodService extends AbstractInputMethodService { mShowInputFlags = 0; mShowInputRequested = false; doHideWindow(); - clearInsetOfPreviousIme(); if (resultReceiver != null) { resultReceiver.send(wasVis != isInputViewShown() ? InputMethodManager.RESULT_HIDDEN @@ -601,7 +592,6 @@ public class InputMethodService extends AbstractInputMethodService { if (dispatchOnShowInputRequested(flags, false)) { showWindow(true); } - clearInsetOfPreviousIme(); // If user uses hard keyboard, IME button should always be shown. setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition); @@ -946,9 +936,6 @@ public class InputMethodService extends AbstractInputMethodService { super.onCreate(); mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); mSettingsObserver = SettingsObserver.createAndRegister(this); - // If the previous IME has occupied non-empty inset in the screen, we need to decide whether - // we continue to use the same size of the inset or update it - mShouldClearInsetOfPreviousIme = (mImm.getInputMethodWindowVisibleHeight() > 0); // TODO(b/111364446) Need to address context lifecycle issue if need to re-create // for update resources & configuration correctly when show soft input // in non-default display. @@ -1882,9 +1869,6 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) Log.v(TAG, "showWindow: showing!"); onWindowShown(); mWindow.show(); - // Put here rather than in onWindowShown() in case people forget to call - // super.onWindowShown(). - mShouldClearInsetOfPreviousIme = false; } } @@ -1934,32 +1918,6 @@ public class InputMethodService extends AbstractInputMethodService { } /** - * Reset the inset occupied the previous IME when and only when - * {@link #mShouldClearInsetOfPreviousIme} is {@code true}. - */ - private void clearInsetOfPreviousIme() { - if (DEBUG) Log.v(TAG, "clearInsetOfPreviousIme() " - + " mShouldClearInsetOfPreviousIme=" + mShouldClearInsetOfPreviousIme); - if (!mShouldClearInsetOfPreviousIme) return; - - clearLastInputMethodWindowForTransition(); - mShouldClearInsetOfPreviousIme = false; - } - - /** - * Tells the system that the IME decided to not show a window and the system no longer needs to - * use the previous IME's inset. - * - * <p>Caveat: {@link android.inputmethodservice.InputMethodService#clearInsetOfPreviousIme()} - * is the only expected caller of this method. Do not depend on this anywhere else.</p> - * - * <p>TODO: We probably need to reconsider how IME should be handled.</p> - */ - private void clearLastInputMethodWindowForTransition() { - mPrivOps.clearLastInputMethodWindowForTransition(); - } - - /** * Called when a new client has bound to the input method. This * may be followed by a series of {@link #onStartInput(EditorInfo, boolean)} * and {@link #onFinishInput()} calls as the user navigates through its @@ -2980,7 +2938,6 @@ public class InputMethodService extends AbstractInputMethodService { + " visibleTopInsets=" + mTmpInsets.visibleTopInsets + " touchableInsets=" + mTmpInsets.touchableInsets + " touchableRegion=" + mTmpInsets.touchableRegion); - p.println(" mShouldClearInsetOfPreviousIme=" + mShouldClearInsetOfPreviousIme); p.println(" mSettingsObserver=" + mSettingsObserver); } } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java new file mode 100644 index 000000000000..aa44eb7e3a26 --- /dev/null +++ b/core/java/android/permission/PermissionManager.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.permission; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; + +import com.android.internal.annotations.Immutable; + +import java.util.Arrays; +import java.util.List; + +/** + * System level service for accessing the permission capabilities of the platform. + * + * @hide + */ +@SystemApi +@SystemService(Context.PERMISSION_SERVICE) +public final class PermissionManager { + /** + * {@link android.content.pm.PackageParser} needs access without having a {@link Context}. + * + * @hide + */ + public static final SplitPermissionInfo[] SPLIT_PERMISSIONS = new SplitPermissionInfo[]{ + // READ_EXTERNAL_STORAGE is always required when an app requests + // WRITE_EXTERNAL_STORAGE, because we can't have an app that has + // write access without read access. The hack here with the target + // target SDK version ensures that this grant is always done. + new SplitPermissionInfo(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, + new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE}, + android.os.Build.VERSION_CODES.CUR_DEVELOPMENT + 1), + new SplitPermissionInfo(android.Manifest.permission.READ_CONTACTS, + new String[]{android.Manifest.permission.READ_CALL_LOG}, + android.os.Build.VERSION_CODES.JELLY_BEAN), + new SplitPermissionInfo(android.Manifest.permission.WRITE_CONTACTS, + new String[]{android.Manifest.permission.WRITE_CALL_LOG}, + android.os.Build.VERSION_CODES.JELLY_BEAN), + new SplitPermissionInfo(Manifest.permission.ACCESS_FINE_LOCATION, + new String[]{android.Manifest.permission.ACCESS_BACKGROUND_LOCATION}, + android.os.Build.VERSION_CODES.P0), + new SplitPermissionInfo(Manifest.permission.ACCESS_COARSE_LOCATION, + new String[]{android.Manifest.permission.ACCESS_BACKGROUND_LOCATION}, + android.os.Build.VERSION_CODES.P0)}; + + private final @NonNull Context mContext; + + /** + * Creates a new instance. + * + * @param context The current context in which to operate. + * @hide + */ + public PermissionManager(@NonNull Context context) { + mContext = context; + } + + /** + * Get list of permissions that have been split into more granular or dependent permissions. + * + * <p>E.g. before {@link android.os.Build.VERSION_CODES#P0} an app that was granted + * {@link Manifest.permission#ACCESS_COARSE_LOCATION} could access he location while it was in + * foreground and background. On platforms after {@link android.os.Build.VERSION_CODES#P0} + * the location permission only grants location access while the app is in foreground. This + * would break apps that target before {@link android.os.Build.VERSION_CODES#P0}. Hence whenever + * such an old app asks for a location permission (i.e. the + * {@link SplitPermissionInfo#getRootPermission()}), then the + * {@link Manifest.permission#ACCESS_BACKGROUND_LOCATION} permission (inside + * {@{@link SplitPermissionInfo#getNewPermissions}) is added. + * + * <p>Note: Regular apps do not have to worry about this. The platform and permission controller + * automatically add the new permissions where needed. + * + * @return All permissions that are split. + */ + public @NonNull List<SplitPermissionInfo> getSplitPermissions() { + return Arrays.asList(SPLIT_PERMISSIONS); + } + + /** + * A permission that was added in a previous API level might have split into several + * permissions. This object describes one such split. + */ + @Immutable + public static final class SplitPermissionInfo { + private final @NonNull String mRootPerm; + private final @NonNull String[] mNewPerms; + private final int mTargetSdk; + + /** + * Get the permission that is split. + */ + public @NonNull String getRootPermission() { + return mRootPerm; + } + + /** + * Get the permissions that are added. + */ + public @NonNull String[] getNewPermissions() { + return mNewPerms; + } + + /** + * Get the target API level when the permission was split. + */ + public int getTargetSdk() { + return mTargetSdk; + } + + private SplitPermissionInfo(@NonNull String rootPerm, @NonNull String[] newPerms, + int targetSdk) { + mRootPerm = rootPerm; + mNewPerms = newPerms; + mTargetSdk = targetSdk; + } + } +} diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index b49a949a36e7..44dfd115f7f2 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -1230,7 +1230,7 @@ public class TextLine { // with the next chunk. So we just save the TextPaint for future comparisons // and use. activePaint.set(wp); - } else if (!wp.hasEqualAttributes(activePaint)) { + } else if (!equalAttributes(wp, activePaint)) { // The style of the present chunk of text is substantially different from the // style of the previous chunk. We need to handle the active piece of text // and restart with the present chunk. @@ -1335,4 +1335,42 @@ public class TextLine { } private static final int TAB_INCREMENT = 20; + + private static boolean equalAttributes(@NonNull TextPaint lp, @NonNull TextPaint rp) { + return lp.getColorFilter() == rp.getColorFilter() + && lp.getMaskFilter() == rp.getMaskFilter() + && lp.getShader() == rp.getShader() + && lp.getTypeface() == rp.getTypeface() + && lp.getXfermode() == rp.getXfermode() + && lp.getTextLocales().equals(rp.getTextLocales()) + && TextUtils.equals(lp.getFontFeatureSettings(), rp.getFontFeatureSettings()) + && TextUtils.equals(lp.getFontVariationSettings(), rp.getFontVariationSettings()) + && lp.getShadowLayerRadius() == rp.getShadowLayerRadius() + && lp.getShadowLayerDx() == rp.getShadowLayerDx() + && lp.getShadowLayerDy() == rp.getShadowLayerDy() + && lp.getShadowLayerColor() == rp.getShadowLayerColor() + && lp.getFlags() == rp.getFlags() + && lp.getHinting() == rp.getHinting() + && lp.getStyle() == rp.getStyle() + && lp.getColor() == rp.getColor() + && lp.getStrokeWidth() == rp.getStrokeWidth() + && lp.getStrokeMiter() == rp.getStrokeMiter() + && lp.getStrokeCap() == rp.getStrokeCap() + && lp.getStrokeJoin() == rp.getStrokeJoin() + && lp.getTextAlign() == rp.getTextAlign() + && lp.isElegantTextHeight() == rp.isElegantTextHeight() + && lp.getTextSize() == rp.getTextSize() + && lp.getTextScaleX() == rp.getTextScaleX() + && lp.getTextSkewX() == rp.getTextSkewX() + && lp.getLetterSpacing() == rp.getLetterSpacing() + && lp.getWordSpacing() == rp.getWordSpacing() + && lp.getHyphenEdit() == rp.getHyphenEdit() + && lp.bgColor == rp.bgColor + && lp.baselineShift == rp.baselineShift + && lp.linkColor == rp.linkColor + && lp.drawableState == rp.drawableState + && lp.density == rp.density + && lp.underlineColor == rp.underlineColor + && lp.underlineThickness == rp.underlineThickness; + } } diff --git a/core/java/android/text/TextPaint.java b/core/java/android/text/TextPaint.java index 7bcc6859b8fc..d5aad33a85e7 100644 --- a/core/java/android/text/TextPaint.java +++ b/core/java/android/text/TextPaint.java @@ -17,7 +17,7 @@ package android.text; import android.annotation.ColorInt; -import android.annotation.NonNull; +import android.annotation.Px; import android.annotation.UnsupportedAppUsage; import android.graphics.Paint; @@ -37,17 +37,14 @@ public class TextPaint extends Paint { public float density = 1.0f; /** * Special value 0 means no custom underline - * @hide */ @ColorInt - @UnsupportedAppUsage public int underlineColor = 0; + /** * Thickness of the underline, in pixels. - * @hide */ - @UnsupportedAppUsage - public float underlineThickness; + public @Px float underlineThickness; public TextPaint() { super(); @@ -78,24 +75,6 @@ public class TextPaint extends Paint { } /** - * Returns true if all attributes, including the attributes inherited from Paint, are equal. - * - * The caller is expected to have checked the trivial cases, like the pointers being equal, - * the objects having different classes, or the parameter being null. - * @hide - */ - public boolean hasEqualAttributes(@NonNull TextPaint other) { - return bgColor == other.bgColor - && baselineShift == other.baselineShift - && linkColor == other.linkColor - && drawableState == other.drawableState - && density == other.density - && underlineColor == other.underlineColor - && underlineThickness == other.underlineThickness - && super.hasEqualAttributes((Paint) other); - } - - /** * Defines a custom underline for this Paint. * @param color underline solid color * @param thickness underline thickness diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 2ee7ab939bd9..9be9ed04d049 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -239,6 +239,12 @@ public final class ViewRootImpl implements ViewParent, final ArrayList<WindowCallbacks> mWindowCallbacks = new ArrayList<>(); @UnsupportedAppUsage final Context mContext; + /** + * TODO(b/116349163): Check if we can merge this into {@link #mContext}. + */ + @NonNull + private Context mDisplayContext; + @UnsupportedAppUsage final IWindowSession mWindowSession; @NonNull Display mDisplay; @@ -532,6 +538,7 @@ public final class ViewRootImpl implements ViewParent, public ViewRootImpl(Context context, Display display) { mContext = context; + mDisplayContext = context.createDisplayContext(display); mWindowSession = WindowManagerGlobal.getWindowSession(); mDisplay = display; mBasePackageName = context.getBasePackageName(); @@ -1249,6 +1256,7 @@ public final class ViewRootImpl implements ViewParent, } else { mDisplay = preferredDisplay; } + mDisplayContext = mContext.createDisplayContext(mDisplay); } void pokeDrawLockIfNeeded() { @@ -2579,7 +2587,7 @@ public final class ViewRootImpl implements ViewParent, .mayUseInputMethod(mWindowAttributes.flags); if (imTarget != mLastWasImTarget) { mLastWasImTarget = imTarget; - InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); + InputMethodManager imm = mDisplayContext.getSystemService(InputMethodManager.class); if (imm != null && imTarget) { imm.onPreWindowFocus(mView, hasWindowFocus); imm.onPostWindowFocus(mView, mView.findFocus(), @@ -2695,7 +2703,7 @@ public final class ViewRootImpl implements ViewParent, mLastWasImTarget = WindowManager.LayoutParams .mayUseInputMethod(mWindowAttributes.flags); - InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); + InputMethodManager imm = mDisplayContext.getSystemService(InputMethodManager.class); if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { imm.onPreWindowFocus(mView, hasWindowFocus); } @@ -4329,7 +4337,8 @@ public final class ViewRootImpl implements ViewParent, enqueueInputEvent(event, null, 0, true); } break; case MSG_CHECK_FOCUS: { - InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); + InputMethodManager imm = + mDisplayContext.getSystemService(InputMethodManager.class); if (imm != null) { imm.checkFocus(); } @@ -4871,7 +4880,7 @@ public final class ViewRootImpl implements ViewParent, @Override protected int onProcess(QueuedInputEvent q) { if (mLastWasImTarget && !isInLocalFocusMode()) { - InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); + InputMethodManager imm = mDisplayContext.getSystemService(InputMethodManager.class); if (imm != null) { final InputEvent event = q.mEvent; if (DEBUG_IMF) Log.v(mTag, "Sending input event to IME: " + event); diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl index c32894dd04f9..2671781a60c1 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl @@ -28,7 +28,6 @@ import com.android.internal.inputmethod.IInputContentUriToken; interface IInputMethodPrivilegedOperations { void setImeWindowStatus(int vis, int backDisposition); void reportStartInput(in IBinder startInputToken); - void clearLastInputMethodWindowForTransition(); IInputContentUriToken createInputContentUriToken(in Uri contentUri, in String packageName); void reportFullscreenMode(boolean fullscreen); void setInputMethod(String id); diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index f0e8dc59d09a..cdb986ab1a78 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -135,22 +135,6 @@ public final class InputMethodPrivilegedOperations { } /** - * Calls {@link IInputMethodPrivilegedOperations#clearLastInputMethodWindowForTransition()}. - */ - @AnyThread - public void clearLastInputMethodWindowForTransition() { - final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); - if (ops == null) { - return; - } - try { - ops.clearLastInputMethodWindowForTransition(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Calls {@link IInputMethodPrivilegedOperations#createInputContentUriToken(Uri, String)}. * * @param contentUri Content URI to which a temporary read permission should be granted diff --git a/core/res/res/drawable/ic_ab_back_material_settings.xml b/core/res/res/drawable/ic_ab_back_material_settings.xml index 7325a4173a7f..938e36d4ecb5 100644 --- a/core/res/res/drawable/ic_ab_back_material_settings.xml +++ b/core/res/res/drawable/ic_ab_back_material_settings.xml @@ -18,11 +18,12 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0" android:autoMirrored="true" - android:tint="?attr/colorControlNormal"> + android:tint="?attr/colorControlNormal" + android:viewportHeight="24" + android:viewportWidth="24"> <path android:fillColor="@color/white" - android:pathData="M20,11H7.62l4.88,-4.88a0.996,0.996 0,1 0,-1.41 -1.41l-6.94,6.94c-0.2,0.2 -0.2,0.51 0,0.71l6.94,6.94a0.996,0.996 0,1 0,1.41 -1.41L7.62,13H20c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z"/> + android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8l8,8l1.41,-1.41L7.83,13H20V11z"/> </vector> + diff --git a/core/res/res/values-television/themes.xml b/core/res/res/values-television/themes.xml index 0712cbcfc024..48b59c70a61e 100644 --- a/core/res/res/values-television/themes.xml +++ b/core/res/res/values-television/themes.xml @@ -15,7 +15,6 @@ --> <resources> <style name="Theme.Dialog.Alert" parent="Theme.Leanback.Light.Dialog.Alert" /> - <style name="Theme.DeviceDefault.Dialog.AppError" parent="Theme.Leanback.Dialog.AppError" /> <style name="Theme.Holo.Dialog.Alert" parent="Theme.Leanback.Dialog.Alert" /> <style name="Theme.Holo.Light.Dialog.Alert" parent="Theme.Leanback.Light.Dialog.Alert" /> <style name="Theme.Material.Dialog.Alert" parent="Theme.Leanback.Dialog.Alert" /> diff --git a/core/res/res/values-television/themes_device_defaults.xml b/core/res/res/values-television/themes_device_defaults.xml index b92c44e9f444..fdd06242c1e0 100644 --- a/core/res/res/values-television/themes_device_defaults.xml +++ b/core/res/res/values-television/themes_device_defaults.xml @@ -15,6 +15,7 @@ --> <resources> <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Leanback.Dialog.Alert" /> + <style name="Theme.DeviceDefault.Dialog.AppError" parent="Theme.Leanback.Dialog.AppError" /> <style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Leanback.Light.Dialog.Alert" /> <!-- TODO(b/116457731): remove colorBackground from colors_material.xml if not used anymore --> diff --git a/core/res/res/values-watch/themes.xml b/core/res/res/values-watch/themes.xml deleted file mode 100644 index 1be47baf4e7f..000000000000 --- a/core/res/res/values-watch/themes.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources> - <!-- Theme for the dialog shown when an app crashes or ANRs. Override to make it dark. --> - <style name="Theme.DeviceDefault.Dialog.AppError" parent="Theme.DeviceDefault.Dialog.Alert"> - <item name="windowContentTransitions">false</item> - <item name="windowActivityTransitions">false</item> - <item name="windowCloseOnTouchOutside">false</item> - </style> -</resources> diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml index a7736e7c3e82..bfba312da016 100644 --- a/core/res/res/values-watch/themes_device_defaults.xml +++ b/core/res/res/values-watch/themes_device_defaults.xml @@ -422,6 +422,13 @@ a similar way. <item name="secondaryContentAlpha">@dimen/secondary_content_alpha_device_default</item> </style> + <!-- Theme for the dialog shown when an app crashes or ANRs. Override to make it dark. --> + <style name="Theme.DeviceDefault.Dialog.AppError" parent="Theme.DeviceDefault.Dialog.Alert"> + <item name="windowContentTransitions">false</item> + <item name="windowActivityTransitions">false</item> + <item name="windowCloseOnTouchOutside">false</item> + </style> + <style name="Theme.DeviceDefault.SearchBar" parent="Theme.Material.SearchBar"> <!-- Color palette Dark --> <item name="colorPrimary">@color/primary_device_default_dark</item> diff --git a/core/tests/coretests/src/android/content/ContentProviderTest.java b/core/tests/coretests/src/android/content/ContentProviderTest.java new file mode 100644 index 000000000000..2142f27ff6d8 --- /dev/null +++ b/core/tests/coretests/src/android/content/ContentProviderTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.content; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.withSettings; + +import android.content.pm.ApplicationInfo; +import android.content.pm.ProviderInfo; +import android.net.Uri; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; + +@RunWith(AndroidJUnit4.class) +public class ContentProviderTest { + + private Context mContext; + private ContentProvider mCp; + + private ApplicationInfo mProviderApp; + private ProviderInfo mProvider; + + @Before + public void setUp() { + mProviderApp = new ApplicationInfo(); + mProviderApp.uid = 10001; + + mProvider = new ProviderInfo(); + mProvider.authority = "com.example"; + mProvider.applicationInfo = mProviderApp; + + mContext = mock(Context.class); + + mCp = mock(ContentProvider.class, withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS)); + mCp.attachInfo(mContext, mProvider); + } + + @Test + public void testValidateIncomingUri_Normal() throws Exception { + assertEquals(Uri.parse("content://com.example/"), + mCp.validateIncomingUri(Uri.parse("content://com.example/"))); + assertEquals(Uri.parse("content://com.example/foo/bar"), + mCp.validateIncomingUri(Uri.parse("content://com.example/foo/bar"))); + assertEquals(Uri.parse("content://com.example/foo%2Fbar"), + mCp.validateIncomingUri(Uri.parse("content://com.example/foo%2Fbar"))); + assertEquals(Uri.parse("content://com.example/foo%2F%2Fbar"), + mCp.validateIncomingUri(Uri.parse("content://com.example/foo%2F%2Fbar"))); + } + + @Test + public void testValidateIncomingUri_Shady() throws Exception { + assertEquals(Uri.parse("content://com.example/"), + mCp.validateIncomingUri(Uri.parse("content://com.example//"))); + assertEquals(Uri.parse("content://com.example/foo/bar/"), + mCp.validateIncomingUri(Uri.parse("content://com.example//foo//bar//"))); + assertEquals(Uri.parse("content://com.example/foo/bar/"), + mCp.validateIncomingUri(Uri.parse("content://com.example/foo///bar/"))); + assertEquals(Uri.parse("content://com.example/foo%2F%2Fbar/baz"), + mCp.validateIncomingUri(Uri.parse("content://com.example/foo%2F%2Fbar//baz"))); + } + + @Test + public void testValidateIncomingUri_NonPath() throws Exception { + // We only touch paths; queries and fragments are left intact + assertEquals(Uri.parse("content://com.example/foo/bar?foo=b//ar#foo=b//ar"), + mCp.validateIncomingUri( + Uri.parse("content://com.example/foo/bar?foo=b//ar#foo=b//ar"))); + } +} diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 33caa00db208..69ff3bca6528 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -592,49 +592,6 @@ public class Paint { mShadowLayerColor = paint.mShadowLayerColor; } - /** - * Returns true if all attributes are equal. - * - * The caller is expected to have checked the trivial cases, like the pointers being equal, - * the objects having different classes, or the parameter being null. - * @hide - */ - public boolean hasEqualAttributes(@NonNull Paint other) { - return mColorFilter == other.mColorFilter - && mMaskFilter == other.mMaskFilter - && mPathEffect == other.mPathEffect - && mShader == other.mShader - && mTypeface == other.mTypeface - && mXfermode == other.mXfermode - && mHasCompatScaling == other.mHasCompatScaling - && mCompatScaling == other.mCompatScaling - && mInvCompatScaling == other.mInvCompatScaling - && mBidiFlags == other.mBidiFlags - && mLocales.equals(other.mLocales) - && TextUtils.equals(mFontFeatureSettings, other.mFontFeatureSettings) - && TextUtils.equals(mFontVariationSettings, other.mFontVariationSettings) - && mShadowLayerRadius == other.mShadowLayerRadius - && mShadowLayerDx == other.mShadowLayerDx - && mShadowLayerDy == other.mShadowLayerDy - && mShadowLayerColor == other.mShadowLayerColor - && getFlags() == other.getFlags() - && getHinting() == other.getHinting() - && getStyle() == other.getStyle() - && getColor() == other.getColor() - && getStrokeWidth() == other.getStrokeWidth() - && getStrokeMiter() == other.getStrokeMiter() - && getStrokeCap() == other.getStrokeCap() - && getStrokeJoin() == other.getStrokeJoin() - && getTextAlign() == other.getTextAlign() - && isElegantTextHeight() == other.isElegantTextHeight() - && getTextSize() == other.getTextSize() - && getTextScaleX() == other.getTextScaleX() - && getTextSkewX() == other.getTextSkewX() - && getLetterSpacing() == other.getLetterSpacing() - && getWordSpacing() == other.getWordSpacing() - && getHyphenEdit() == other.getHyphenEdit(); - } - /** @hide */ @UnsupportedAppUsage public void setCompatibilityScaling(float factor) { @@ -1395,6 +1352,38 @@ public class Paint { } /** + * Returns the blur radius of the shadow layer. + * @see #setShadowLayer(float,float,float,int) + */ + public float getShadowLayerRadius() { + return mShadowLayerRadius; + } + + /** + * Returns the x offset of the shadow layer. + * @see #setShadowLayer(float,float,float,int) + */ + public float getShadowLayerDx() { + return mShadowLayerDx; + } + + /** + * Returns the y offset of the shadow layer. + * @see #setShadowLayer(float,float,float,int) + */ + public float getShadowLayerDy() { + return mShadowLayerDy; + } + + /** + * Returns the color of the shadow layer. + * @see #setShadowLayer(float,float,float,int) + */ + public @ColorInt int getShadowLayerColor() { + return mShadowLayerColor; + } + + /** * Return the paint's Align value for drawing text. This controls how the * text is positioned relative to its origin. LEFT align means that all of * the text will be drawn to the right of its origin (i.e. the origin diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 943dd84eccf9..ee4c95445bff 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -14,6 +14,7 @@ android_library { static_libs: [ "SettingsLibHelpUtils", "SettingsLibRestrictedLockUtils", + "SettingsLibAppPreference", ], // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES diff --git a/packages/SettingsLib/AppPreference/Android.bp b/packages/SettingsLib/AppPreference/Android.bp new file mode 100644 index 000000000000..b56181d75e6a --- /dev/null +++ b/packages/SettingsLib/AppPreference/Android.bp @@ -0,0 +1,13 @@ +android_library { + name: "SettingsLibAppPreference", + + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + + libs: [ + "androidx.annotation_annotation", + "androidx.preference_preference", + ], + sdk_version: "system_current", + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/AppPreference/AndroidManifest.xml b/packages/SettingsLib/AppPreference/AndroidManifest.xml new file mode 100644 index 000000000000..7e71308444cd --- /dev/null +++ b/packages/SettingsLib/AppPreference/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.widget.apppreference"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml new file mode 100644 index 000000000000..6d35550f1b77 --- /dev/null +++ b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:attr/selectableItemBackground" + android:gravity="center_vertical" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + + <LinearLayout + android:id="@+id/icon_frame" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="start|center_vertical" + android:minWidth="56dp" + android:orientation="horizontal" + android:paddingEnd="8dp" + android:paddingTop="4dp" + android:paddingBottom="4dp"> + <ImageView + android:id="@android:id/icon" + android:layout_width="@dimen/secondary_app_icon_size" + android:layout_height="@dimen/secondary_app_icon_size"/> + </LinearLayout> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:orientation="vertical" + android:paddingTop="16dp" + android:paddingBottom="16dp"> + + <TextView android:id="@android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:textAppearance="@android:style/TextAppearance.Material.Subhead" + android:ellipsize="marquee" + android:fadingEdge="horizontal"/> + + <LinearLayout + android:id="@+id/summary_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone"> + <TextView android:id="@android:id/summary" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textAppearance="@android:style/TextAppearance.Material.Small" + android:textAlignment="viewStart" + android:textColor="?android:attr/textColorSecondary"/> + + <TextView android:id="@+id/appendix" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textAppearance="@android:style/TextAppearance.Material.Small" + android:textAlignment="viewEnd" + android:textColor="?android:attr/textColorSecondary" + android:maxLines="1" + android:ellipsize="end"/> + </LinearLayout> + <ProgressBar + android:id="@android:id/progress" + style="?android:attr/progressBarStyleHorizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:max="100" + android:visibility="gone"/> + </LinearLayout> + + <LinearLayout + android:id="@android:id/widget_frame" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center" + android:minWidth="64dp" + android:orientation="vertical"/> + +</LinearLayout> diff --git a/packages/SettingsLib/AppPreference/res/values/dimens.xml b/packages/SettingsLib/AppPreference/res/values/dimens.xml new file mode 100644 index 000000000000..e2a7a191f9de --- /dev/null +++ b/packages/SettingsLib/AppPreference/res/values/dimens.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources> + <dimen name="secondary_app_icon_size">32dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/apppreference/AppPreference.java b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/apppreference/AppPreference.java new file mode 100644 index 000000000000..593b6f5b35f4 --- /dev/null +++ b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/apppreference/AppPreference.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.widget.apppreference; + +import android.content.Context; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ProgressBar; + +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +public class AppPreference extends Preference { + + private int mProgress; + private boolean mProgressVisible; + + public AppPreference(Context context) { + super(context); + setLayoutResource(R.layout.preference_app); + } + + public AppPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.preference_app); + } + + public void setProgress(int amount) { + mProgress = amount; + mProgressVisible = true; + notifyChanged(); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder view) { + super.onBindViewHolder(view); + + view.findViewById(R.id.summary_container) + .setVisibility(TextUtils.isEmpty(getSummary()) ? View.GONE : View.VISIBLE); + final ProgressBar progress = (ProgressBar) view.findViewById(android.R.id.progress); + if (mProgressVisible) { + progress.setProgress(mProgress); + progress.setVisibility(View.VISIBLE); + } else { + progress.setVisibility(View.GONE); + } + } +}
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index a71041045df2..58feef55bd29 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -102,7 +102,7 @@ public class A2dpProfile implements LocalBluetoothProfile { BluetoothProfile.A2DP); } - public boolean isConnectable() { + public boolean accessProfileEnabled() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java index e13e566ec901..988062de0a37 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java @@ -96,7 +96,7 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { BluetoothProfile.A2DP_SINK); } - public boolean isConnectable() { + public boolean accessProfileEnabled() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index b9f7323b1a9e..2887a08cad06 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -29,8 +29,6 @@ import android.os.SystemClock; import android.text.TextUtils; import android.util.Log; -import androidx.annotation.VisibleForTesting; - import com.android.settingslib.R; import java.util.ArrayList; @@ -39,6 +37,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import androidx.annotation.VisibleForTesting; + /** * CachedBluetoothDevice represents a remote Bluetooth device. It contains * attributes of the device (such as the address, name, RSSI, etc.) and @@ -48,6 +48,10 @@ import java.util.List; public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { private static final String TAG = "CachedBluetoothDevice"; + // See mConnectAttempted + private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000; + private static final long MAX_HOGP_DELAY_FOR_AUTO_CONNECT = 30000; + private final Context mContext; private final BluetoothAdapter mLocalAdapter; private final LocalBluetoothProfileManager mProfileManager; @@ -78,17 +82,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> private final static String MESSAGE_REJECTION_COUNT_PREFS_NAME = "bluetooth_message_reject"; - public long getHiSyncId() { - return mHiSyncId; - } - - public void setHiSyncId(long id) { - if (BluetoothUtils.D) { - Log.d(TAG, "setHiSyncId: mDevice " + mDevice + ", id " + id); - } - mHiSyncId = id; - } - /** * Last time a bt profile auto-connect was attempted. * If an ACTION_UUID intent comes in within @@ -97,14 +90,22 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> */ private long mConnectAttempted; - // See mConnectAttempted - private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000; - private static final long MAX_HOGP_DELAY_FOR_AUTO_CONNECT = 30000; - // Active device state private boolean mIsActiveDeviceA2dp = false; private boolean mIsActiveDeviceHeadset = false; private boolean mIsActiveDeviceHearingAid = false; + + CachedBluetoothDevice(Context context, LocalBluetoothProfileManager profileManager, + BluetoothDevice device) { + mContext = context; + mLocalAdapter = BluetoothAdapter.getDefaultAdapter(); + mProfileManager = profileManager; + mDevice = device; + mProfileConnectionState = new HashMap<>(); + fillData(); + mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID; + } + /** * Describes the current device and profile for logging. * @@ -161,18 +162,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> fetchActiveDevices(); } - CachedBluetoothDevice(Context context, - LocalBluetoothProfileManager profileManager, - BluetoothDevice device) { - mContext = context; - mLocalAdapter = BluetoothAdapter.getDefaultAdapter(); - mProfileManager = profileManager; - mDevice = device; - mProfileConnectionState = new HashMap<LocalBluetoothProfile, Integer>(); - fillData(); - mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID; - } - public void disconnect() { for (LocalBluetoothProfile profile : mProfiles) { disconnect(profile); @@ -204,6 +193,17 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> connectWithoutResettingTimer(connectAllProfiles); } + public long getHiSyncId() { + return mHiSyncId; + } + + public void setHiSyncId(long id) { + if (BluetoothUtils.D) { + Log.d(TAG, "setHiSyncId: mDevice " + mDevice + ", id " + id); + } + mHiSyncId = id; + } + void onBondingDockConnect() { // Attempt to connect if UUIDs are available. Otherwise, // we will connect when the ACTION_UUID intent arrives. @@ -226,7 +226,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> int preferredProfiles = 0; for (LocalBluetoothProfile profile : mProfiles) { - if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) { + if (connectAllProfiles ? profile.accessProfileEnabled() : profile.isAutoConnectable()) { if (profile.isPreferred(mDevice)) { ++preferredProfiles; connectInt(profile); @@ -300,14 +300,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> return true; } - /** - * Return true if user initiated pairing on this device. The message text is - * slightly different for local vs. remote initiated pairing dialogs. - */ - boolean isUserInitiatedPairing() { - return mDevice.isBondingInitiatedLocally(); - } - public void unpair() { int state = getBondState(); @@ -669,7 +661,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> List<LocalBluetoothProfile> connectableProfiles = new ArrayList<LocalBluetoothProfile>(); for (LocalBluetoothProfile profile : mProfiles) { - if (profile.isConnectable()) { + if (profile.accessProfileEnabled()) { connectableProfiles.add(profile); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java index 2dd8eaf7021d..62507f58426f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -103,7 +103,7 @@ public class HeadsetProfile implements LocalBluetoothProfile { BluetoothProfile.HEADSET); } - public boolean isConnectable() { + public boolean accessProfileEnabled() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java index 1eeb4f058d1d..8bc0acf5f824 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java @@ -100,7 +100,7 @@ public class HearingAidProfile implements LocalBluetoothProfile { new HearingAidServiceListener(), BluetoothProfile.HEARING_AID); } - public boolean isConnectable() { + public boolean accessProfileEnabled() { return false; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java index 4b6a22c9b2c8..4879144a5994 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java @@ -104,7 +104,7 @@ final class HfpClientProfile implements LocalBluetoothProfile { } @Override - public boolean isConnectable() { + public boolean accessProfileEnabled() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java index c8d4fc84f4b4..61e5b6b3e125 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java @@ -94,7 +94,7 @@ public class HidDeviceProfile implements LocalBluetoothProfile { } @Override - public boolean isConnectable() { + public boolean accessProfileEnabled() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java index fe6b22224819..75d16db13efe 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java @@ -93,7 +93,7 @@ public class HidProfile implements LocalBluetoothProfile { BluetoothProfile.HID_HOST); } - public boolean isConnectable() { + public boolean accessProfileEnabled() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java index 0447f378fca1..4b0ca7434f9a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java @@ -26,9 +26,9 @@ import android.bluetooth.BluetoothDevice; public interface LocalBluetoothProfile { /** - * Returns true if the user can initiate a connection, false otherwise. + * Return {@code true} if the user can initiate a connection for this profile in UI. */ - boolean isConnectable(); + boolean accessProfileEnabled(); /** * Returns true if the user can enable auto connection for this profile. diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java index 7ad2e28c84b5..1e22f440b5f8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java @@ -105,7 +105,7 @@ public final class MapClientProfile implements LocalBluetoothProfile { new MapClientServiceListener(), BluetoothProfile.MAP_CLIENT); } - public boolean isConnectable() { + public boolean accessProfileEnabled() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java index caea04f87a50..758202412c93 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java @@ -104,7 +104,7 @@ public class MapProfile implements LocalBluetoothProfile { BluetoothProfile.MAP); } - public boolean isConnectable() { + public boolean accessProfileEnabled() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java index dfd16224ef8f..e1e5dbe29a1a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java @@ -32,7 +32,7 @@ final class OppProfile implements LocalBluetoothProfile { // Order of this profile in device profiles list private static final int ORDINAL = 2; - public boolean isConnectable() { + public boolean accessProfileEnabled() { return false; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java index 02afe8db7201..7b811624a6f3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java @@ -78,7 +78,7 @@ public class PanProfile implements LocalBluetoothProfile { BluetoothProfile.PAN); } - public boolean isConnectable() { + public boolean accessProfileEnabled() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java index 8fefb2fc01b8..1f15601f2756 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java @@ -107,7 +107,7 @@ public final class PbapClientProfile implements LocalBluetoothProfile { new PbapClientServiceListener(), BluetoothProfile.PBAP_CLIENT); } - public boolean isConnectable() { + public boolean accessProfileEnabled() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java index e9d8cb5a4b2b..adef0841cb2a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java @@ -80,7 +80,7 @@ public class PbapServerProfile implements LocalBluetoothProfile { BluetoothPbap pbap = new BluetoothPbap(context, new PbapServiceListener()); } - public boolean isConnectable() { + public boolean accessProfileEnabled() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java index 61602c6463ee..9a6f104fadd5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java @@ -106,7 +106,7 @@ final class SapProfile implements LocalBluetoothProfile { BluetoothProfile.SAP); } - public boolean isConnectable() { + public boolean accessProfileEnabled() { return true; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java index 0ca2e87de336..ede248b85083 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java @@ -52,6 +52,7 @@ public class SettingsLibRobolectricTestRunner extends RobolectricTestRunner { @Override public List<ResourcePath> getIncludedResourcePaths() { final List<ResourcePath> paths = super.getIncludedResourcePaths(); + paths.add(resourcePath("file:frameworks/base/packages/SettingsLib/AppPreference/res")); paths.add(resourcePath("file:frameworks/base/packages/SettingsLib/HelpUtils/res")); paths.add(resourcePath("file:frameworks/base/packages/SettingsLib/RestrictedLockUtils/res")); paths.add(resourcePath("file:frameworks/base/packages/SettingsLib/res")); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/apppreference/AppPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/apppreference/AppPreferenceTest.java new file mode 100644 index 000000000000..10c9dfbe6067 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/apppreference/AppPreferenceTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.widget.apppreference; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.view.View; + +import androidx.preference.PreferenceViewHolder; + +import com.android.settingslib.SettingsLibRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsLibRobolectricTestRunner.class) +public class AppPreferenceTest { + + private Context mContext; + private View mRootView; + private AppPreference mPref; + private PreferenceViewHolder mHolder; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mRootView = View.inflate(mContext, R.layout.preference_app, null /* parent */); + mHolder = PreferenceViewHolder.createInstanceForTests(mRootView); + mPref = new AppPreference(mContext); + } + + @Test + public void setProgress_showProgress() { + mPref.setProgress(1); + mPref.onBindViewHolder(mHolder); + + assertThat(mHolder.findViewById(android.R.id.progress).getVisibility()) + .isEqualTo(View.VISIBLE); + } + + @Test + public void setSummary_showSummaryContainer() { + mPref.setSummary("test"); + mPref.onBindViewHolder(mHolder); + + assertThat(mHolder.findViewById(R.id.summary_container).getVisibility()) + .isEqualTo(View.VISIBLE); + } + + @Test + public void noSummary_hideSummaryContainer() { + mPref.setSummary(null); + mPref.onBindViewHolder(mHolder); + + assertThat(mHolder.findViewById(R.id.summary_container).getVisibility()) + .isEqualTo(View.GONE); + } + + @Test + public void foobar_testName() { + float iconSize = mContext.getResources().getDimension(R.dimen.secondary_app_icon_size); + assertThat(Float.floatToIntBits(iconSize)).isEqualTo(Float.floatToIntBits(32)); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index 36b234704592..c5664605dcef 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -16,11 +16,15 @@ package com.android.systemui.doze; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.os.Build; import android.os.Handler; import android.os.Trace; import android.os.UserHandle; @@ -31,7 +35,12 @@ import com.android.internal.annotations.VisibleForTesting; /** * Controls the screen brightness when dozing. */ -public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListener { +public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachine.Part, + SensorEventListener { + protected static final String ACTION_AOD_BRIGHTNESS = + "com.android.systemui.doze.AOD_BRIGHTNESS"; + protected static final String BRIGHTNESS_BUCKET = "brightness_bucket"; + private final Context mContext; private final DozeMachine.Service mDozeService; private final DozeHost mDozeHost; @@ -40,36 +49,52 @@ public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListen private final Sensor mLightSensor; private final int[] mSensorToBrightness; private final int[] mSensorToScrimOpacity; + private final boolean mDebuggable; private boolean mRegistered; private int mDefaultDozeBrightness; private boolean mPaused = false; private int mLastSensorValue = -1; + /** + * Debug value used for emulating various display brightness buckets: + * + * {@code am broadcast -p com.android.systemui -a com.android.systemui.doze.AOD_BRIGHTNESS + * --ei brightness_bucket 1} + */ + private int mDebugBrightnessBucket = -1; + + @VisibleForTesting public DozeScreenBrightness(Context context, DozeMachine.Service service, SensorManager sensorManager, Sensor lightSensor, DozeHost host, Handler handler, int defaultDozeBrightness, int[] sensorToBrightness, - int[] sensorToScrimOpacity) { + int[] sensorToScrimOpacity, boolean debuggable) { mContext = context; mDozeService = service; mSensorManager = sensorManager; mLightSensor = lightSensor; mDozeHost = host; mHandler = handler; + mDebuggable = debuggable; mDefaultDozeBrightness = defaultDozeBrightness; mSensorToBrightness = sensorToBrightness; mSensorToScrimOpacity = sensorToScrimOpacity; + + if (mDebuggable) { + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_AOD_BRIGHTNESS); + mContext.registerReceiverAsUser(this, UserHandle.ALL, filter, null, null); + } } - @VisibleForTesting public DozeScreenBrightness(Context context, DozeMachine.Service service, SensorManager sensorManager, Sensor lightSensor, DozeHost host, Handler handler, AlwaysOnDisplayPolicy policy) { this(context, service, sensorManager, lightSensor, host, handler, context.getResources().getInteger( com.android.internal.R.integer.config_screenBrightnessDoze), - policy.screenBrightnessArray, policy.dimmingScrimArray); + policy.screenBrightnessArray, policy.dimmingScrimArray, Build.IS_DEBUGGABLE); } @Override @@ -87,7 +112,7 @@ public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListen resetBrightnessToDefault(); break; case FINISH: - setLightSensorEnabled(false); + onDestroy(); break; } if (newState != DozeMachine.State.FINISH) { @@ -95,6 +120,13 @@ public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListen } } + private void onDestroy() { + setLightSensorEnabled(false); + if (mDebuggable) { + mContext.unregisterReceiver(this); + } + } + @Override public void onSensorChanged(SensorEvent event) { Trace.beginSection("DozeScreenBrightness.onSensorChanged" + event.values[0]); @@ -110,7 +142,9 @@ public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListen private void updateBrightnessAndReady() { if (mRegistered) { - int brightness = computeBrightness(mLastSensorValue); + int sensorValue = mDebugBrightnessBucket == -1 + ? mLastSensorValue : mDebugBrightnessBucket; + int brightness = computeBrightness(sensorValue); boolean brightnessReady = brightness > 0; if (brightnessReady) { mDozeService.setDozeScreenBrightness(clampToUserSetting(brightness)); @@ -125,7 +159,7 @@ public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListen scrimOpacity = 255; } else if (brightnessReady) { // Only unblank scrim once brightness is ready. - scrimOpacity = computeScrimOpacity(mLastSensorValue); + scrimOpacity = computeScrimOpacity(sensorValue); } if (scrimOpacity >= 0) { mDozeHost.setAodDimmingScrim(scrimOpacity / 255f); @@ -184,4 +218,9 @@ public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListen } } + @Override + public void onReceive(Context context, Intent intent) { + mDebugBrightnessBucket = intent.getIntExtra(BRIGHTNESS_BUCKET, -1); + updateBrightnessAndReady(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 757c821f37e3..4988f07ca3f3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1938,6 +1938,12 @@ public class KeyguardViewMediator extends SystemUI { mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE); } + + // TODO(b/113914868): investigation log for disappearing home button + Log.d(TAG, "adjustStatusBarLocked (b/113914868): mShowing=" + mShowing + + " mStatusBarManager=" + mStatusBarManager + " mOccluded=" + + mOccluded + " isSecure=" + isSecure() + " force=" + forceHideHomeRecentsButtons); + if (mStatusBarManager == null) { Log.w(TAG, "Could not get status bar manager"); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index aebcb9f27e24..71b35e043f77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -595,6 +595,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav boolean disableHome = ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0); + // TODO(b/113914868): investigation log for disappearing home button + Log.i(TAG, "updateNavButtonIcons (b/113914868): home disabled=" + disableHome + + " mDisabledFlags=" + mDisabledFlags); + // Always disable recents when alternate car mode UI is active. boolean disableRecent = mUseCarModeUi || !isOverviewEnabled(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java index eaa0dcf88588..1cf73b00d219 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java @@ -33,6 +33,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import android.content.Intent; import android.os.PowerManager; import android.os.UserHandle; import android.provider.Settings; @@ -71,7 +72,8 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { mSensor = mSensorManager.getFakeLightSensor(); mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, mSensor.getSensor(), mHostFake, null /* handler */, - DEFAULT_BRIGHTNESS, SENSOR_TO_BRIGHTNESS, SENSOR_TO_OPACITY); + DEFAULT_BRIGHTNESS, SENSOR_TO_BRIGHTNESS, SENSOR_TO_OPACITY, + true /* debuggable */); } @Test @@ -93,6 +95,19 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { } @Test + public void testAod_usesDebugValue() throws Exception { + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + mScreen.transitionTo(INITIALIZED, DOZE_AOD); + + Intent intent = new Intent(DozeScreenBrightness.ACTION_AOD_BRIGHTNESS); + intent.putExtra(DozeScreenBrightness.BRIGHTNESS_BUCKET, 1); + mScreen.onReceive(mContext, intent); + mSensor.sendSensorEvent(3); + + assertEquals(1, mServiceFake.screenBrightness); + } + + @Test public void testAod_usesLightSensorRespectingUserSetting() throws Exception { int maxBrightness = 3; Settings.System.putIntForUser(mContext.getContentResolver(), @@ -160,7 +175,8 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { public void testNullSensor() throws Exception { mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, null /* sensor */, mHostFake, null /* handler */, - DEFAULT_BRIGHTNESS, SENSOR_TO_BRIGHTNESS, SENSOR_TO_OPACITY); + DEFAULT_BRIGHTNESS, SENSOR_TO_BRIGHTNESS, SENSOR_TO_OPACITY, + true /* debuggable */); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index de85dcb16adb..90c10fdcbfde 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -6524,6 +6524,31 @@ message MetricsEvent { // Tag of a field for the length of a text FIELD_AUTOFILL_TEXT_LEN = 1572; + // Action: the notification assistant is changing a notification + // OS: Q + NOTIFICATION_ASSISTANT_ADJUSTMENT = 1573; + + // Subtype: The people attached to a notification was changed + ADJUSTMENT_KEY_PEOPLE = 1574; + + // Subtype: The snooze options attached to a notification was changed + ADJUSTMENT_KEY_SNOOZE_CRITERIA = 1575; + + // Subtype: The group of a notification was changed + ADJUSTMENT_KEY_GROUP_KEY = 1576; + + // Subtype: The user sentiment of a notification was changed + ADJUSTMENT_KEY_USER_SENTIMENT = 1577; + + // Subtype: New actions have been added to a notification + ADJUSTMENT_KEY_SMART_ACTIONS = 1578; + + // Subtype: New smart replies have been added to a notification + ADJUSTMENT_KEY_SMART_REPLIES = 1579; + + // Subtype: The importance of a notification has been changed + ADJUSTMENT_KEY_IMPORTANCE = 1580; + // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. diff --git a/services/backup/java/com/android/server/backup/keyvalue/AgentException.java b/services/backup/java/com/android/server/backup/keyvalue/AgentException.java new file mode 100644 index 000000000000..e2ca35116bdc --- /dev/null +++ b/services/backup/java/com/android/server/backup/keyvalue/AgentException.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.keyvalue; + +/** + * This represents something wrong with a specific package. For example: + * <ul> + * <li>Package unknown. + * <li>Package is not eligible for backup anymore. + * <li>Backup agent timed out. + * <li>Backup agent wrote protected keys. + * <li>... + * </ul> + * + * @see KeyValueBackupTask + * @see TaskException + */ +class AgentException extends BackupException { + static AgentException transitory() { + return new AgentException(/* transitory */ true); + } + + static AgentException transitory(Exception cause) { + return new AgentException(/* transitory */ true, cause); + } + + static AgentException permanent() { + return new AgentException(/* transitory */ false); + } + + static AgentException permanent(Exception cause) { + return new AgentException(/* transitory */ false, cause); + } + + private final boolean mTransitory; + + private AgentException(boolean transitory) { + mTransitory = transitory; + } + + private AgentException(boolean transitory, Exception cause) { + super(cause); + mTransitory = transitory; + } + + boolean isTransitory() { + return mTransitory; + } +} diff --git a/services/backup/java/com/android/server/backup/keyvalue/BackupException.java b/services/backup/java/com/android/server/backup/keyvalue/BackupException.java new file mode 100644 index 000000000000..27b2d35b13ca --- /dev/null +++ b/services/backup/java/com/android/server/backup/keyvalue/BackupException.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.keyvalue; + +import android.util.AndroidException; + +/** + * Key-value backup task exception. + * + * @see AgentException + * @see TaskException + */ +class BackupException extends AndroidException { + BackupException() {} + + BackupException(Exception cause) { + super(cause); + } +} diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java index 54e6b1d82f74..bb8a1d1339a7 100644 --- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java +++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java @@ -54,7 +54,7 @@ import java.util.List; public class KeyValueBackupReporter { @VisibleForTesting static final String TAG = "KeyValueBackupTask"; private static final boolean DEBUG = BackupManagerService.DEBUG; - @VisibleForTesting static final boolean MORE_DEBUG = BackupManagerService.MORE_DEBUG || true; + @VisibleForTesting static final boolean MORE_DEBUG = BackupManagerService.MORE_DEBUG || false; static void onNewThread(String threadName) { if (DEBUG) { @@ -153,16 +153,18 @@ public class KeyValueBackupReporter { mObserver, packageName, BackupManager.ERROR_BACKUP_NOT_ALLOWED); } - void onBindAgentError(SecurityException e) { - Slog.d(TAG, "Error in bind/backup", e); - } - void onAgentUnknown(String packageName) { Slog.d(TAG, "Package does not exist, skipping"); BackupObserverUtils.sendBackupOnPackageResult( mObserver, packageName, BackupManager.ERROR_PACKAGE_NOT_FOUND); } + void onBindAgentError(String packageName, SecurityException e) { + Slog.d(TAG, "Error in bind/backup", e); + BackupObserverUtils.sendBackupOnPackageResult( + mObserver, packageName, BackupManager.ERROR_AGENT_FAILURE); + } + void onAgentError(String packageName) { if (MORE_DEBUG) { Slog.i(TAG, "Agent failure for " + packageName + ", re-staging"); @@ -190,6 +192,8 @@ public class KeyValueBackupReporter { void onCallAgentDoBackupError(String packageName, boolean callingAgent, Exception e) { if (callingAgent) { Slog.e(TAG, "Error invoking agent on " + packageName + ": " + e); + BackupObserverUtils.sendBackupOnPackageResult( + mObserver, packageName, BackupManager.ERROR_AGENT_FAILURE); } else { Slog.e(TAG, "Error before invoking agent on " + packageName + ": " + e); } @@ -220,12 +224,8 @@ public class KeyValueBackupReporter { } } - void onReadAgentDataError(String packageName, IOException e) { - Slog.w(TAG, "Unable read backup data for " + packageName + ": " + e); - } - - void onWriteWidgetDataError(String packageName, IOException e) { - Slog.w(TAG, "Unable to save widget data for " + packageName + ": " + e); + void onAgentDataError(String packageName, IOException e) { + Slog.w(TAG, "Unable to read/write agent data for " + packageName + ": " + e); } void onDigestError(NoSuchAlgorithmException e) { @@ -243,16 +243,12 @@ public class KeyValueBackupReporter { } } - void onSendDataToTransport(String packageName) { + void onTransportPerformBackup(String packageName) { if (MORE_DEBUG) { Slog.v(TAG, "Sending non-empty data to transport for " + packageName); } } - void onNonIncrementalAndNonIncrementalRequired() { - Slog.e(TAG, "Transport requested non-incremental but already the case"); - } - void onEmptyData(PackageInfo packageInfo) { if (MORE_DEBUG) { Slog.i(TAG, "No backup data written, not calling transport"); @@ -302,13 +298,20 @@ public class KeyValueBackupReporter { /* extras */ null); } + void onPackageBackupNonIncrementalAndNonIncrementalRequired(String packageName) { + Slog.e(TAG, "Transport requested non-incremental but already the case"); + BackupObserverUtils.sendBackupOnPackageResult( + mObserver, packageName, BackupManager.ERROR_TRANSPORT_ABORTED); + EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, packageName); + } + void onPackageBackupTransportFailure(String packageName) { BackupObserverUtils.sendBackupOnPackageResult( mObserver, packageName, BackupManager.ERROR_TRANSPORT_ABORTED); EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, packageName); } - void onPackageBackupError(String packageName, Exception e) { + void onPackageBackupTransportError(String packageName, Exception e) { Slog.e(TAG, "Transport error backing up " + packageName, e); BackupObserverUtils.sendBackupOnPackageResult( mObserver, packageName, BackupManager.ERROR_TRANSPORT_ABORTED); diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java index e915ce16a2ef..6904b3fc6b9c 100644 --- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java +++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java @@ -16,6 +16,7 @@ package com.android.server.backup.keyvalue; +import static android.app.ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL; import static android.os.ParcelFileDescriptor.MODE_CREATE; import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; @@ -25,8 +26,8 @@ import static com.android.server.backup.BackupManagerService.KEY_WIDGET_STATE; import static com.android.server.backup.BackupManagerService.OP_PENDING; import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP; +import android.annotation.IntDef; import android.annotation.Nullable; -import android.app.ApplicationThreadConstants; import android.app.IBackupAgent; import android.app.backup.BackupAgent; import android.app.backup.BackupDataInput; @@ -47,7 +48,6 @@ import android.os.RemoteException; import android.os.SELinux; import android.os.UserHandle; import android.os.WorkSource; -import android.util.Pair; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -77,6 +77,8 @@ import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -173,10 +175,8 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { private static final AtomicInteger THREAD_COUNT = new AtomicInteger(); private static final String BLANK_STATE_FILE_NAME = "blank_state"; private static final String PM_PACKAGE = BackupManagerService.PACKAGE_MANAGER_SENTINEL; - @VisibleForTesting - public static final String STAGING_FILE_SUFFIX = ".data"; - @VisibleForTesting - public static final String NEW_STATE_FILE_SUFFIX = ".new"; + @VisibleForTesting public static final String STAGING_FILE_SUFFIX = ".data"; + @VisibleForTesting public static final String NEW_STATE_FILE_SUFFIX = ".new"; /** * Creates a new {@link KeyValueBackupTask} for key-value backup operation, spins up a new @@ -244,13 +244,13 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { private final int mCurrentOpToken; private final File mStateDirectory; private final File mDataDirectory; + private final File mBlankStateFile; private final List<String> mOriginalQueue; private final List<String> mQueue; private final List<String> mPendingFullBackups; private final Object mQueueLock; @Nullable private final DataChangedJournal mJournal; - private int mStatus; @Nullable private PerformFullTransportBackupTask mFullBackupTask; @Nullable private IBackupAgent mAgent; @Nullable private PackageInfo mCurrentPackage; @@ -316,6 +316,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { mDataDirectory = mBackupManagerService.getDataDir(); mCurrentOpToken = backupManagerService.generateRandomIntegerToken(); mQueueLock = mBackupManagerService.getQueueLock(); + mBlankStateFile = new File(mStateDirectory, BLANK_STATE_FILE_NAME); } private void registerTask() { @@ -331,45 +332,43 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { public void run() { Process.setThreadPriority(THREAD_PRIORITY); - boolean processQueue = startTask(); - while (processQueue && !mQueue.isEmpty() && !mCancelled) { - String packageName = mQueue.remove(0); - if (PM_PACKAGE.equals(packageName)) { - processQueue = backupPm(); - } else { - processQueue = backupPackage(packageName); + int status = BackupTransport.TRANSPORT_OK; + try { + startTask(); + while (!mQueue.isEmpty() && !mCancelled) { + String packageName = mQueue.remove(0); + try { + if (PM_PACKAGE.equals(packageName)) { + backupPm(); + } else { + backupPackage(packageName); + } + } catch (AgentException e) { + if (e.isTransitory()) { + // We try again this package in the next backup pass. + mBackupManagerService.dataChangedImpl(packageName); + } + } } + } catch (TaskException e) { + if (e.isStateCompromised()) { + mBackupManagerService.resetBackupState(mStateDirectory); + } + revertTask(); + status = e.getStatus(); } - finishTask(); + finishTask(status); } - /** Returns whether to consume next queue package. */ - private boolean handleAgentResult(@Nullable PackageInfo packageInfo, RemoteResult result) { - if (result == RemoteResult.FAILED_THREAD_INTERRUPTED) { - // Not an explicit cancel, we need to flag it. - mCancelled = true; - mReporter.onAgentCancelled(packageInfo); - cleanUpAgentForAgentError(); - return false; - } - if (result == RemoteResult.FAILED_CANCELLED) { - mReporter.onAgentCancelled(packageInfo); - cleanUpAgentForAgentError(); - return false; - } - if (result == RemoteResult.FAILED_TIMED_OUT) { - mReporter.onAgentTimedOut(packageInfo); - cleanUpAgentForAgentError(); - return true; - } - Preconditions.checkState(result.isPresent()); - long agentResult = result.get(); - if (agentResult == BackupAgent.RESULT_ERROR) { - mReporter.onAgentResultError(packageInfo); - cleanUpAgentForAgentError(); - return true; + /** Returns transport status. */ + private int sendDataToTransport(@Nullable PackageInfo packageInfo) + throws AgentException, TaskException { + try { + return sendDataToTransport(); + } catch (IOException e) { + mReporter.onAgentDataError(packageInfo.packageName, e); + throw TaskException.causedBy(e); } - return sendDataToTransport(); } @Override @@ -378,11 +377,10 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { @Override public void operationComplete(long unusedResult) {} - /** Returns whether to consume next queue package. */ - private boolean startTask() { + private void startTask() throws TaskException { if (mBackupManagerService.isBackupOperationInProgress()) { mReporter.onSkipBackup(); - return false; + throw TaskException.create(); } // Unfortunately full backup task constructor registers the task with BMS, so we have to @@ -390,11 +388,9 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { mFullBackupTask = createFullBackupTask(mPendingFullBackups); registerTask(); - mStatus = BackupTransport.TRANSPORT_OK; - if (mQueue.isEmpty() && mPendingFullBackups.isEmpty()) { mReporter.onEmptyQueueAtStart(); - return false; + return; } // We only backup PM if it was explicitly in the queue or if it's incremental. boolean backupPm = mQueue.remove(PM_PACKAGE) || !mNonIncremental; @@ -415,20 +411,18 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { if (pmState.length() <= 0) { mReporter.onInitializeTransport(transportName); mBackupManagerService.resetBackupState(mStateDirectory); - mStatus = transport.initializeDevice(); - mReporter.onTransportInitialized(mStatus); + int status = transport.initializeDevice(); + mReporter.onTransportInitialized(status); + if (status != BackupTransport.TRANSPORT_OK) { + throw TaskException.stateCompromised(); + } } + } catch (TaskException e) { + throw e; } catch (Exception e) { mReporter.onInitializeTransportError(e); - mStatus = BackupTransport.TRANSPORT_ERROR; - } - - if (mStatus != BackupTransport.TRANSPORT_OK) { - mBackupManagerService.resetBackupState(mStateDirectory); - return false; + throw TaskException.stateCompromised(); } - - return true; } private PerformFullTransportBackupTask createFullBackupTask(List<String> packages) { @@ -446,120 +440,82 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { mUserInitiated); } - /** Returns whether to consume next queue package. */ - private boolean backupPm() { - RemoteResult agentResult = null; - try { - mCurrentPackage = new PackageInfo(); - mCurrentPackage.packageName = PM_PACKAGE; - - // Since PM is running in the system process we can set up its agent directly. - BackupAgent pmAgent = mBackupManagerService.makeMetadataAgent(); - mAgent = IBackupAgent.Stub.asInterface(pmAgent.onBind()); + private void backupPm() throws TaskException { + mReporter.onStartPackageBackup(PM_PACKAGE); + mCurrentPackage = new PackageInfo(); + mCurrentPackage.packageName = PM_PACKAGE; - Pair<Integer, RemoteResult> statusAndResult = extractAgentData(PM_PACKAGE, mAgent); - mStatus = statusAndResult.first; - agentResult = statusAndResult.second; - } catch (Exception e) { + try { + extractPmAgentData(mCurrentPackage); + int status = sendDataToTransport(mCurrentPackage); + cleanUpAgentForTransportStatus(status); + } catch (AgentException | TaskException e) { mReporter.onExtractPmAgentDataError(e); - mStatus = BackupTransport.TRANSPORT_ERROR; - } - - if (mStatus != BackupTransport.TRANSPORT_OK) { - // In this case either extractAgentData() already made the agent clean-up or we haven't - // prepared the state for calling the agent, in either case we don't need to clean-up. - mBackupManagerService.resetBackupState(mStateDirectory); - return false; + cleanUpAgentForError(e); + // PM agent failure is task failure. + throw TaskException.stateCompromised(e); } - - Preconditions.checkNotNull(agentResult); - return handleAgentResult(mCurrentPackage, agentResult); } - /** Returns whether to consume next queue package. */ - private boolean backupPackage(String packageName) { + private void backupPackage(String packageName) throws AgentException, TaskException { mReporter.onStartPackageBackup(packageName); - mStatus = BackupTransport.TRANSPORT_OK; + mCurrentPackage = getPackageForBackup(packageName); - // Verify that the requested app is eligible for key-value backup. - RemoteResult agentResult = null; try { - mCurrentPackage = mPackageManager.getPackageInfo( - packageName, PackageManager.GET_SIGNING_CERTIFICATES); - ApplicationInfo applicationInfo = mCurrentPackage.applicationInfo; - if (!AppBackupUtils.appIsEligibleForBackup(applicationInfo, mPackageManager)) { - // The manifest has changed. This won't happen again because the app won't be - // requesting further backups. - mReporter.onPackageNotEligibleForBackup(packageName); - return true; - } - - if (AppBackupUtils.appGetsFullBackup(mCurrentPackage)) { - // Initially enqueued for key-value backup, but only supports full-backup now. - mReporter.onPackageEligibleForFullBackup(packageName); - return true; - } - - if (AppBackupUtils.appIsStopped(applicationInfo)) { - // Just as it won't receive broadcasts, we won't run it for backup. - mReporter.onPackageStopped(packageName); - return true; - } + extractAgentData(mCurrentPackage); + int status = sendDataToTransport(mCurrentPackage); + cleanUpAgentForTransportStatus(status); + } catch (AgentException | TaskException e) { + cleanUpAgentForError(e); + throw e; + } + } - try { - mBackupManagerService.setWorkSource(new WorkSource(applicationInfo.uid)); - IBackupAgent agent = - mBackupManagerService.bindToAgentSynchronous( - applicationInfo, - ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL); - if (agent != null) { - mAgent = agent; - Pair<Integer, RemoteResult> statusAndResult = - extractAgentData(packageName, agent); - mStatus = statusAndResult.first; - agentResult = statusAndResult.second; - } else { - // Timeout waiting for the agent to bind. - mStatus = BackupTransport.AGENT_ERROR; - } - } catch (SecurityException e) { - mReporter.onBindAgentError(e); - mStatus = BackupTransport.AGENT_ERROR; - } + private PackageInfo getPackageForBackup(String packageName) throws AgentException { + final PackageInfo packageInfo; + try { + packageInfo = + mPackageManager.getPackageInfo( + packageName, PackageManager.GET_SIGNING_CERTIFICATES); } catch (PackageManager.NameNotFoundException e) { - mStatus = BackupTransport.AGENT_UNKNOWN; - } finally { - mBackupManagerService.setWorkSource(null); + mReporter.onAgentUnknown(packageName); + throw AgentException.permanent(e); } + ApplicationInfo applicationInfo = packageInfo.applicationInfo; + if (!AppBackupUtils.appIsEligibleForBackup(applicationInfo, mPackageManager)) { + mReporter.onPackageNotEligibleForBackup(packageName); + throw AgentException.permanent(); + } + if (AppBackupUtils.appGetsFullBackup(packageInfo)) { + mReporter.onPackageEligibleForFullBackup(packageName); + throw AgentException.permanent(); + } + if (AppBackupUtils.appIsStopped(applicationInfo)) { + mReporter.onPackageStopped(packageName); + throw AgentException.permanent(); + } + return packageInfo; + } - if (mStatus != BackupTransport.TRANSPORT_OK) { - // In this case either extractAgentData() already made the agent clean-up or we haven't - // prepared the state for calling the agent, in either case we don't need to clean-up. - Preconditions.checkState(mAgent == null); - - if (mStatus == BackupTransport.AGENT_ERROR) { + private IBackupAgent bindAgent(PackageInfo packageInfo) throws AgentException { + String packageName = packageInfo.packageName; + final IBackupAgent agent; + try { + agent = + mBackupManagerService.bindToAgentSynchronous( + packageInfo.applicationInfo, BACKUP_MODE_INCREMENTAL); + if (agent == null) { mReporter.onAgentError(packageName); - mBackupManagerService.dataChangedImpl(packageName); - mStatus = BackupTransport.TRANSPORT_OK; - return true; + throw AgentException.transitory(); } - - if (mStatus == BackupTransport.AGENT_UNKNOWN) { - mReporter.onAgentUnknown(packageName); - mStatus = BackupTransport.TRANSPORT_OK; - return true; - } - - // Transport-level failure, re-enqueue everything. - revertTask(); - return false; + } catch (SecurityException e) { + mReporter.onBindAgentError(packageName, e); + throw AgentException.transitory(e); } - - Preconditions.checkNotNull(agentResult); - return handleAgentResult(mCurrentPackage, agentResult); + return agent; } - private void finishTask() { + private void finishTask(int status) { // Mark packages that we couldn't backup as pending backup. for (String packageName : mQueue) { mBackupManagerService.dataChangedImpl(packageName); @@ -576,7 +532,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { // If we succeeded and this is the first time we've done a backup, we can record the current // backup dataset token. long currentToken = mBackupManagerService.getCurrentToken(); - if ((mStatus == BackupTransport.TRANSPORT_OK) && (currentToken == 0)) { + if ((status == BackupTransport.TRANSPORT_OK) && (currentToken == 0)) { try { IBackupTransport transport = mTransportClient.connectOrThrow(callerLogString); mBackupManagerService.setCurrentToken(transport.getCurrentRestoreSet()); @@ -589,9 +545,14 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { synchronized (mQueueLock) { mBackupManagerService.setBackupRunning(false); - if (mStatus == BackupTransport.TRANSPORT_NOT_INITIALIZED) { + if (status == BackupTransport.TRANSPORT_NOT_INITIALIZED) { mReporter.onTransportNotInitialized(); - triggerTransportInitializationLocked(); + try { + triggerTransportInitializationLocked(); + } catch (Exception e) { + mReporter.onPendingInitializeTransportError(e); + status = BackupTransport.TRANSPORT_ERROR; + } } } @@ -605,7 +566,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { } if (!mCancelled - && mStatus == BackupTransport.TRANSPORT_OK + && status == BackupTransport.TRANSPORT_OK && mFullBackupTask != null && !mPendingFullBackups.isEmpty()) { mReporter.onStartFullBackup(mPendingFullBackups); @@ -621,7 +582,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { mFullBackupTask.unregisterTask(); } mTaskFinishedListener.onFinished(callerLogString); - mReporter.onBackupFinished(getBackupFinishedStatus(mCancelled, mStatus)); + mReporter.onBackupFinished(getBackupFinishedStatus(mCancelled, status)); mBackupManagerService.getWakelock().release(); } @@ -642,17 +603,12 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { } @GuardedBy("mQueueLock") - private void triggerTransportInitializationLocked() { - try { - IBackupTransport transport = - mTransportClient.connectOrThrow("KVBT.triggerTransportInitializationLocked"); - mBackupManagerService.getPendingInits().add(transport.name()); - deletePmStateFile(); - mBackupManagerService.backupNow(); - } catch (Exception e) { - mReporter.onPendingInitializeTransportError(e); - mStatus = BackupTransport.TRANSPORT_ERROR; - } + private void triggerTransportInitializationLocked() throws Exception { + IBackupTransport transport = + mTransportClient.connectOrThrow("KVBT.triggerTransportInitializationLocked"); + mBackupManagerService.getPendingInits().add(transport.name()); + deletePmStateFile(); + mBackupManagerService.backupNow(); } /** Removes PM state, triggering initialization in the next key-value task. */ @@ -660,35 +616,69 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { new File(mStateDirectory, PM_PACKAGE).delete(); } + /** Same as {@link #extractAgentData(PackageInfo)}, but only for PM package. */ + private void extractPmAgentData(PackageInfo packageInfo) throws AgentException, TaskException { + Preconditions.checkArgument(packageInfo.packageName.equals(PM_PACKAGE)); + BackupAgent pmAgent = mBackupManagerService.makeMetadataAgent(); + mAgent = IBackupAgent.Stub.asInterface(pmAgent.onBind()); + extractAgentData(packageInfo, mAgent); + } + /** - * Returns a {@link Pair}. The first of the pair contains the status. In case the status is - * {@link BackupTransport#TRANSPORT_OK}, the second of the pair contains the agent result, - * otherwise {@code null}. + * Binds to the agent and extracts its backup data. If this method returns, the data in {@code + * mBackupData} is ready to be sent to the transport, otherwise it will throw. + * + * <p>This method leaves agent resources (agent binder, files and file-descriptors) opened that + * need to be cleaned up after terminating, either successfully or exceptionally. This clean-up + * can be done with methods {@link #cleanUpAgentForTransportStatus(int)} and {@link + * #cleanUpAgentForError(BackupException)}, depending on whether data was successfully sent to + * the transport or not. It's the caller responsibility to do the clean-up or delegate it. */ - private Pair<Integer, RemoteResult> extractAgentData(String packageName, IBackupAgent agent) { + private void extractAgentData(PackageInfo packageInfo) throws AgentException, TaskException { + mBackupManagerService.setWorkSource(new WorkSource(packageInfo.applicationInfo.uid)); + try { + mAgent = bindAgent(packageInfo); + extractAgentData(packageInfo, mAgent); + } finally { + mBackupManagerService.setWorkSource(null); + } + } + + /** + * Calls agent {@link IBackupAgent#doBackup(ParcelFileDescriptor, ParcelFileDescriptor, + * ParcelFileDescriptor, long, IBackupCallback, int)} and waits for the result. If this method + * returns, the data in {@code mBackupData} is ready to be sent to the transport, otherwise it + * will throw. + * + * <p>This method creates files and file-descriptors for the agent that need to be deleted and + * closed after terminating, either successfully or exceptionally. This clean-up can be done + * with methods {@link #cleanUpAgentForTransportStatus(int)} and {@link + * #cleanUpAgentForError(BackupException)}, depending on whether data was successfully sent to + * the transport or not. It's the caller responsibility to do the clean-up or delegate it. + */ + private void extractAgentData(PackageInfo packageInfo, IBackupAgent agent) + throws AgentException, TaskException { + String packageName = packageInfo.packageName; mReporter.onExtractAgentData(packageName); - File blankStateFile = new File(mStateDirectory, BLANK_STATE_FILE_NAME); mSavedStateFile = new File(mStateDirectory, packageName); mBackupDataFile = new File(mDataDirectory, packageName + STAGING_FILE_SUFFIX); mNewStateFile = new File(mStateDirectory, packageName + NEW_STATE_FILE_SUFFIX); mReporter.onAgentFilesReady(mBackupDataFile); - mSavedState = null; - mBackupData = null; - mNewState = null; - boolean callingAgent = false; final RemoteResult agentResult; try { - File savedStateFileForAgent = (mNonIncremental) ? blankStateFile : mSavedStateFile; + File savedStateFileForAgent = (mNonIncremental) ? mBlankStateFile : mSavedStateFile; // MODE_CREATE to make an empty file if necessary - mSavedState = ParcelFileDescriptor.open( - savedStateFileForAgent, MODE_READ_ONLY | MODE_CREATE); - mBackupData = ParcelFileDescriptor.open( - mBackupDataFile, MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE); - mNewState = ParcelFileDescriptor.open( - mNewStateFile, MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE); + mSavedState = + ParcelFileDescriptor.open(savedStateFileForAgent, MODE_READ_ONLY | MODE_CREATE); + mBackupData = + ParcelFileDescriptor.open( + mBackupDataFile, MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE); + mNewState = + ParcelFileDescriptor.open( + mNewStateFile, MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE); if (!SELinux.restorecon(mBackupDataFile)) { mReporter.onRestoreconFailed(mBackupDataFile); @@ -713,15 +703,40 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { "doBackup()"); } catch (Exception e) { mReporter.onCallAgentDoBackupError(packageName, callingAgent, e); - cleanUpAgentForAgentError(); - // TODO: Remove the check on callingAgent when RemoteCall supports local agent calls. - int status = - callingAgent ? BackupTransport.AGENT_ERROR : BackupTransport.TRANSPORT_ERROR; - return Pair.create(status, null); + if (callingAgent) { + throw AgentException.transitory(e); + } else { + throw TaskException.create(); + } + } finally { + mBlankStateFile.delete(); } - blankStateFile.delete(); + checkAgentResult(packageInfo, agentResult); + } - return Pair.create(BackupTransport.TRANSPORT_OK, agentResult); + private void checkAgentResult(PackageInfo packageInfo, RemoteResult result) + throws AgentException, TaskException { + if (result == RemoteResult.FAILED_THREAD_INTERRUPTED) { + // Not an explicit cancel, we need to flag it. + mCancelled = true; + mReporter.onAgentCancelled(packageInfo); + throw TaskException.create(); + } + if (result == RemoteResult.FAILED_CANCELLED) { + mReporter.onAgentCancelled(packageInfo); + throw TaskException.create(); + } + if (result == RemoteResult.FAILED_TIMED_OUT) { + mReporter.onAgentTimedOut(packageInfo); + throw AgentException.transitory(); + } + Preconditions.checkState(result.isPresent()); + long resultCode = result.get(); + if (resultCode == BackupAgent.RESULT_ERROR) { + mReporter.onAgentResultError(packageInfo); + throw AgentException.transitory(); + } + Preconditions.checkState(resultCode == BackupAgent.RESULT_SUCCESS); } private void agentFail(IBackupAgent agent, String message) { @@ -801,94 +816,79 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { } } - /** Returns whether to consume next queue package. */ - private boolean sendDataToTransport() { + /** Returns transport status. */ + private int sendDataToTransport() throws AgentException, TaskException, IOException { Preconditions.checkState(mBackupData != null); + checkBackupData(mCurrentPackage.applicationInfo, mBackupDataFile); String packageName = mCurrentPackage.packageName; - ApplicationInfo applicationInfo = mCurrentPackage.applicationInfo; + writeWidgetPayloadIfAppropriate(mBackupData.getFileDescriptor(), packageName); - boolean writingWidgetData = false; - try { - if (!validateBackupData(applicationInfo, mBackupDataFile)) { - cleanUpAgentForAgentError(); - return true; - } - writingWidgetData = true; - writeWidgetPayloadIfAppropriate(mBackupData.getFileDescriptor(), packageName); - } catch (IOException e) { - if (writingWidgetData) { - mReporter.onWriteWidgetDataError(packageName, e); - } else { - mReporter.onReadAgentDataError(packageName, e); - } - cleanUpAgentForAgentError(); - revertTask(); - return false; + boolean nonIncremental = mSavedStateFile.length() == 0; + int status = transportPerformBackup(mCurrentPackage, mBackupDataFile, nonIncremental); + handleTransportStatus(status, packageName, mBackupDataFile.length()); + return status; + } + + private int transportPerformBackup( + PackageInfo packageInfo, File backupDataFile, boolean nonIncremental) + throws TaskException { + String packageName = packageInfo.packageName; + long size = backupDataFile.length(); + if (size <= 0) { + mReporter.onEmptyData(packageInfo); + return BackupTransport.TRANSPORT_OK; } - boolean nonIncremental = mSavedStateFile.length() == 0; - long size = mBackupDataFile.length(); - if (size > 0) { - try (ParcelFileDescriptor backupData = - ParcelFileDescriptor.open(mBackupDataFile, MODE_READ_ONLY)) { - IBackupTransport transport = - mTransportClient.connectOrThrow("KVBT.sendDataToTransport()"); - mReporter.onSendDataToTransport(packageName); - int flags = getPerformBackupFlags(mUserInitiated, nonIncremental); + int status; + try (ParcelFileDescriptor backupData = + ParcelFileDescriptor.open(backupDataFile, MODE_READ_ONLY)) { + IBackupTransport transport = + mTransportClient.connectOrThrow("KVBT.transportPerformBackup()"); + mReporter.onTransportPerformBackup(packageName); + int flags = getPerformBackupFlags(mUserInitiated, nonIncremental); - mStatus = transport.performBackup(mCurrentPackage, backupData, flags); - if (mStatus == BackupTransport.TRANSPORT_OK) { - mStatus = transport.finishBackup(); - } - } catch (Exception e) { - mReporter.onPackageBackupError(packageName, e); - mStatus = BackupTransport.TRANSPORT_ERROR; + status = transport.performBackup(packageInfo, backupData, flags); + if (status == BackupTransport.TRANSPORT_OK) { + status = transport.finishBackup(); } - } else { - mReporter.onEmptyData(mCurrentPackage); - mStatus = BackupTransport.TRANSPORT_OK; + } catch (Exception e) { + mReporter.onPackageBackupTransportError(packageName, e); + throw TaskException.causedBy(e); } - if (nonIncremental - && mStatus == BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED) { - mReporter.onNonIncrementalAndNonIncrementalRequired(); - mStatus = BackupTransport.TRANSPORT_ERROR; + if (nonIncremental && status == BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED) { + mReporter.onPackageBackupNonIncrementalAndNonIncrementalRequired(packageName); + throw TaskException.create(); } - - boolean processQueue = handleTransportStatus(mStatus, packageName, size); - // We might report quota exceeded to the agent in handleTransportStatus() above, so we - // only clean-up after it. - cleanUpAgentForTransportStatus(mStatus); - return processQueue; + return status; } - /** Returns whether to consume next queue package. */ - private boolean handleTransportStatus(int status, String packageName, long size) { + private void handleTransportStatus(int status, String packageName, long size) + throws TaskException, AgentException { if (status == BackupTransport.TRANSPORT_OK) { mReporter.onPackageBackupComplete(packageName, size); - return true; - } - if (status == BackupTransport.TRANSPORT_PACKAGE_REJECTED) { - mReporter.onPackageBackupRejected(packageName); - return true; + return; } if (status == BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED) { mReporter.onPackageBackupNonIncrementalRequired(mCurrentPackage); // Immediately retry the current package. mQueue.add(0, packageName); - return true; + return; + } + if (status == BackupTransport.TRANSPORT_PACKAGE_REJECTED) { + mReporter.onPackageBackupRejected(packageName); + throw AgentException.permanent(); } if (status == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) { mReporter.onPackageBackupQuotaExceeded(packageName); agentDoQuotaExceeded(mAgent, packageName, size); - return true; + throw AgentException.permanent(); } // Any other error here indicates a transport-level failure. mReporter.onPackageBackupTransportFailure(packageName); - revertTask(); - return false; + throw TaskException.forStatus(status); } private void agentDoQuotaExceeded(@Nullable IBackupAgent agent, String packageName, long size) { @@ -908,19 +908,17 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { } /** - * For system apps and pseudo-apps always return {@code true}. For regular apps returns whether - * {@code backupDataFile} doesn't have any protected keys. - * - * <p>If the app has attempted to write any protected keys we also crash them. + * For system apps and pseudo-apps never throws. For regular apps throws {@link AgentException} + * if {@code backupDataFile} has any protected keys, also crashing the app. */ - private boolean validateBackupData( - @Nullable ApplicationInfo applicationInfo, File backupDataFile) throws IOException { + private void checkBackupData(@Nullable ApplicationInfo applicationInfo, File backupDataFile) + throws IOException, AgentException { if (applicationInfo == null || (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { // System apps and pseudo-apps can write what they want. - return true; + return; } try (ParcelFileDescriptor backupData = - ParcelFileDescriptor.open(backupDataFile, MODE_READ_ONLY)) { + ParcelFileDescriptor.open(backupDataFile, MODE_READ_ONLY)) { BackupDataInput backupDataInput = new BackupDataInput(backupData.getFileDescriptor()); while (backupDataInput.readNextHeader()) { String key = backupDataInput.getKey(); @@ -928,12 +926,11 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { mReporter.onAgentIllegalKey(mCurrentPackage, key); // Crash them if they wrote any protected keys. agentFail(mAgent, "Illegal backup key: " + key); - return false; + throw AgentException.permanent(); } backupDataInput.skipEntityData(); } } - return true; } private int getPerformBackupFlags(boolean userInitiated, boolean nonIncremental) { @@ -1009,44 +1006,39 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { } } - /** Cleans-up after having called the agent. */ - private void cleanUpAgentForTransportStatus(int status) { - updateFiles(status); - cleanUpAgent(); - } - - /** Cleans-up if we failed to call the agent. */ - private void cleanUpAgentForAgentError() { - mBackupDataFile.delete(); - mNewStateFile.delete(); - cleanUpAgent(); + /** + * Cleans up agent resources opened by {@link #extractAgentData(PackageInfo)} for exceptional + * case. + * + * <p>Note: Declaring exception parameter so that the caller only calls this when an exception + * is thrown. + */ + private void cleanUpAgentForError(BackupException exception) { + cleanUpAgent(StateTransaction.DISCARD_NEW); } - private void updateFiles(int status) { + /** + * Cleans up agent resources opened by {@link #extractAgentData(PackageInfo)} according to + * transport status returned in {@link #sendDataToTransport(PackageInfo)}. + */ + private void cleanUpAgentForTransportStatus(int status) { switch (status) { case BackupTransport.TRANSPORT_OK: - mBackupDataFile.delete(); - mNewStateFile.renameTo(mSavedStateFile); + cleanUpAgent(StateTransaction.COMMIT_NEW); break; case BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED: - mSavedStateFile.delete(); - mBackupDataFile.delete(); - mNewStateFile.delete(); + cleanUpAgent(StateTransaction.DISCARD_ALL); break; default: - // Includes: - // * BackupTransport.TRANSPORT_PACKAGE_REJECTED - // * BackupTransport.TRANSPORT_QUOTA_EXCEEDED - // * BackupTransport.TRANSPORT_ERROR - mBackupDataFile.delete(); - mNewStateFile.delete(); - break; + // All other transport statuses are properly converted to agent or task exceptions. + throw new AssertionError(); } } - /** Cleans-up file-descriptors and unbinds agent. */ - private void cleanUpAgent() { - mAgent = null; + private void cleanUpAgent(@StateTransaction int stateTransaction) { + applyStateTransaction(stateTransaction); + mBackupDataFile.delete(); + mBlankStateFile.delete(); tryCloseFileDescriptor(mSavedState, "old state"); tryCloseFileDescriptor(mBackupData, "backup data"); tryCloseFileDescriptor(mNewState, "new state"); @@ -1058,6 +1050,24 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { if (mCurrentPackage.applicationInfo != null) { mBackupManagerService.unbindAgent(mCurrentPackage.applicationInfo); } + mAgent = null; + } + + private void applyStateTransaction(@StateTransaction int stateTransaction) { + switch (stateTransaction) { + case StateTransaction.COMMIT_NEW: + mNewStateFile.renameTo(mSavedStateFile); + break; + case StateTransaction.DISCARD_NEW: + mNewStateFile.delete(); + break; + case StateTransaction.DISCARD_ALL: + mSavedStateFile.delete(); + mNewStateFile.delete(); + break; + default: + throw new IllegalArgumentException("Unknown state transaction " + stateTransaction); + } } private void tryCloseFileDescriptor(@Nullable Closeable closeable, String logName) { @@ -1079,4 +1089,16 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { mPendingCall = null; return result; } + + @IntDef({ + StateTransaction.COMMIT_NEW, + StateTransaction.DISCARD_NEW, + StateTransaction.DISCARD_ALL, + }) + @Retention(RetentionPolicy.SOURCE) + private @interface StateTransaction { + int COMMIT_NEW = 0; + int DISCARD_NEW = 1; + int DISCARD_ALL = 2; + } } diff --git a/services/backup/java/com/android/server/backup/keyvalue/TaskException.java b/services/backup/java/com/android/server/backup/keyvalue/TaskException.java new file mode 100644 index 000000000000..08d289556ca3 --- /dev/null +++ b/services/backup/java/com/android/server/backup/keyvalue/TaskException.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.keyvalue; + +import android.app.backup.BackupTransport; + +import com.android.internal.util.Preconditions; + +/** + * The key-value backup task has failed, no more packages will be processed and we shouldn't attempt + * any more backups now. These can be caused by transport failures (as opposed to agent failures). + * + * @see KeyValueBackupTask + * @see AgentException + */ +class TaskException extends BackupException { + private static final int DEFAULT_STATUS = BackupTransport.TRANSPORT_ERROR; + + static TaskException stateCompromised() { + return new TaskException(/* stateCompromised */ true, DEFAULT_STATUS); + } + + static TaskException stateCompromised(Exception cause) { + if (cause instanceof TaskException) { + TaskException exception = (TaskException) cause; + return new TaskException(cause, /* stateCompromised */ true, exception.getStatus()); + } + return new TaskException(cause, /* stateCompromised */ true, DEFAULT_STATUS); + } + + static TaskException forStatus(int status) { + Preconditions.checkArgument( + status != BackupTransport.TRANSPORT_OK, "Exception based on TRANSPORT_OK"); + return new TaskException(/* stateCompromised */ false, status); + } + + static TaskException causedBy(Exception cause) { + if (cause instanceof TaskException) { + return (TaskException) cause; + } + return new TaskException(cause, /* stateCompromised */ false, DEFAULT_STATUS); + } + + static TaskException create() { + return new TaskException(/* stateCompromised */ false, DEFAULT_STATUS); + } + + private final boolean mStateCompromised; + private final int mStatus; + + private TaskException(Exception cause, boolean stateCompromised, int status) { + super(cause); + mStateCompromised = stateCompromised; + mStatus = status; + } + + private TaskException(boolean stateCompromised, int status) { + mStateCompromised = stateCompromised; + mStatus = status; + } + + boolean isStateCompromised() { + return mStateCompromised; + } + + int getStatus() { + return mStatus; + } +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 8c7fc849b79e..6e3ea9fad599 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -20892,7 +20892,8 @@ public class ActivityManagerService extends IActivityManager.Stub memoryStat.pgmajfault, memoryStat.rssInBytes, memoryStat.cacheInBytes, - memoryStat.swapInBytes); + memoryStat.swapInBytes, + memoryStat.rssHighWatermarkInBytes); processMemoryStates.add(processMemoryState); } } diff --git a/services/core/java/com/android/server/am/MemoryStatUtil.java b/services/core/java/com/android/server/am/MemoryStatUtil.java index aad890b8bd74..228c71d91b8f 100644 --- a/services/core/java/com/android/server/am/MemoryStatUtil.java +++ b/services/core/java/com/android/server/am/MemoryStatUtil.java @@ -37,18 +37,25 @@ import java.util.regex.Pattern; * Static utility methods related to {@link MemoryStat}. */ final class MemoryStatUtil { + static final int BYTES_IN_KILOBYTE = 1024; + private static final String TAG = TAG_WITH_CLASS_NAME ? "MemoryStatUtil" : TAG_AM; /** True if device has per-app memcg */ - private static final Boolean DEVICE_HAS_PER_APP_MEMCG = + private static final boolean DEVICE_HAS_PER_APP_MEMCG = SystemProperties.getBoolean("ro.config.per_app_memcg", false); /** Path to check if device has memcg */ private static final String MEMCG_TEST_PATH = "/dev/memcg/apps/memory.stat"; /** Path to memory stat file for logging app start memory state */ private static final String MEMORY_STAT_FILE_FMT = "/dev/memcg/apps/uid_%d/pid_%d/memory.stat"; + /** Path to memory max usage file for logging app memory state */ + private static final String MEMORY_MAX_USAGE_FILE_FMT = + "/dev/memcg/apps/uid_%d/pid_%d/memory.max_usage_in_bytes"; /** Path to procfs stat file for logging app start memory state */ private static final String PROC_STAT_FILE_FMT = "/proc/%d/stat"; + /** Path to procfs status file for logging app memory state */ + private static final String PROC_STATUS_FILE_FMT = "/proc/%d/status"; private static final Pattern PGFAULT = Pattern.compile("total_pgfault (\\d+)"); private static final Pattern PGMAJFAULT = Pattern.compile("total_pgmajfault (\\d+)"); @@ -56,6 +63,9 @@ final class MemoryStatUtil { private static final Pattern CACHE_IN_BYTES = Pattern.compile("total_cache (\\d+)"); private static final Pattern SWAP_IN_BYTES = Pattern.compile("total_swap (\\d+)"); + private static final Pattern RSS_HIGH_WATERMARK_IN_BYTES = + Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB"); + private static final int PGFAULT_INDEX = 9; private static final int PGMAJFAULT_INDEX = 11; private static final int RSS_IN_BYTES_INDEX = 23; @@ -80,8 +90,15 @@ final class MemoryStatUtil { */ @Nullable static MemoryStat readMemoryStatFromMemcg(int uid, int pid) { - final String path = String.format(Locale.US, MEMORY_STAT_FILE_FMT, uid, pid); - return parseMemoryStatFromMemcg(readFileContents(path)); + final String statPath = String.format(Locale.US, MEMORY_STAT_FILE_FMT, uid, pid); + MemoryStat stat = parseMemoryStatFromMemcg(readFileContents(statPath)); + if (stat == null) { + return null; + } + String maxUsagePath = String.format(Locale.US, MEMORY_MAX_USAGE_FILE_FMT, uid, pid); + stat.rssHighWatermarkInBytes = parseMemoryMaxUsageFromMemCg( + readFileContents(maxUsagePath)); + return stat; } /** @@ -91,8 +108,14 @@ final class MemoryStatUtil { */ @Nullable static MemoryStat readMemoryStatFromProcfs(int pid) { - final String path = String.format(Locale.US, PROC_STAT_FILE_FMT, pid); - return parseMemoryStatFromProcfs(readFileContents(path)); + final String statPath = String.format(Locale.US, PROC_STAT_FILE_FMT, pid); + MemoryStat stat = parseMemoryStatFromProcfs(readFileContents(statPath)); + if (stat == null) { + return null; + } + final String statusPath = String.format(Locale.US, PROC_STATUS_FILE_FMT, pid); + stat.rssHighWatermarkInBytes = parseVmHWMFromProcfs(readFileContents(statusPath)); + return stat; } private static String readFileContents(String path) { @@ -113,7 +136,7 @@ final class MemoryStatUtil { /** * Parses relevant statistics out from the contents of a memory.stat file in memcg. */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + @VisibleForTesting @Nullable static MemoryStat parseMemoryStatFromMemcg(String memoryStatContents) { if (memoryStatContents == null || memoryStatContents.isEmpty()) { @@ -135,10 +158,18 @@ final class MemoryStatUtil { return memoryStat; } + @VisibleForTesting + static long parseMemoryMaxUsageFromMemCg(String memoryMaxUsageContents) { + if (memoryMaxUsageContents == null || memoryMaxUsageContents.isEmpty()) { + return 0; + } + return Long.valueOf(memoryMaxUsageContents); + } + /** - * Parses relevant statistics out from the contents of a /proc/pid/stat file in procfs. + * Parses relevant statistics out from the contents of the /proc/pid/stat file in procfs. */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + @VisibleForTesting @Nullable static MemoryStat parseMemoryStatFromProcfs(String procStatContents) { if (procStatContents == null || procStatContents.isEmpty()) { @@ -158,6 +189,20 @@ final class MemoryStatUtil { } /** + * Parses RSS high watermark out from the contents of the /proc/pid/status file in procfs. The + * returned value is in bytes. + */ + @VisibleForTesting + static long parseVmHWMFromProcfs(String procStatusContents) { + if (procStatusContents == null || procStatusContents.isEmpty()) { + return 0; + } + Matcher m = RSS_HIGH_WATERMARK_IN_BYTES.matcher(procStatusContents); + // Convert value read from /proc/pid/status from kilobytes to bytes. + return m.find() ? Long.valueOf(m.group(1)) * BYTES_IN_KILOBYTE : 0; + } + + /** * Returns whether per-app memcg is available on device. */ static boolean hasMemcg() { @@ -175,5 +220,7 @@ final class MemoryStatUtil { long cacheInBytes; /** Number of bytes of swap usage */ long swapInBytes; + /** Number of bytes of peak anonymous and swap cache memory */ + long rssHighWatermarkInBytes; } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 6f5f90a781e3..a9b0d5c42f73 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1976,7 +1976,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub throw new IllegalArgumentException("Unknown id: " + mCurMethodId); } - unbindCurrentMethodLocked(true); + unbindCurrentMethodLocked(); mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE); mCurIntent.setComponent(info.getComponent()); @@ -2020,7 +2020,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurMethod = IInputMethod.Stub.asInterface(service); if (mCurToken == null) { Slog.w(TAG, "Service connected without a token!"); - unbindCurrentMethodLocked(false); + unbindCurrentMethodLocked(); return; } if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); @@ -2059,7 +2059,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub channel.dispose(); } - void unbindCurrentMethodLocked(boolean savePosition) { + void unbindCurrentMethodLocked() { if (mVisibleBound) { mContext.unbindService(mVisibleConnection); mVisibleBound = false; @@ -2076,10 +2076,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Slog.v(TAG, "Removing window token: " + mCurToken + " for display: " + mCurTokenDisplayId); } - if ((mImeWindowVis & InputMethodService.IME_ACTIVE) != 0 && savePosition) { - // The current IME is shown. Hence an IME switch (transition) is happening. - mWindowManagerInternal.saveLastInputMethodWindowForTransition(); - } mIWindowManager.removeWindowToken(mCurToken, mCurTokenDisplayId); } catch (RemoteException e) { } @@ -2094,7 +2090,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub void resetCurrentMethodAndClient( /* @InputMethodClient.UnbindReason */ final int unbindClientReason) { mCurMethodId = null; - unbindCurrentMethodLocked(false); + unbindCurrentMethodLocked(); unbindCurrentClientLocked(unbindClientReason); } @@ -2865,7 +2861,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final int newFocusDisplayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken); if (newFocusDisplayId != mCurTokenDisplayId) { - unbindCurrentMethodLocked(false); + unbindCurrentMethodLocked(); } } } else if (isTextEditor && doAutoShow && (softInputMode & @@ -3237,19 +3233,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @BinderThread - private void clearLastInputMethodWindowForTransition(IBinder token) { - if (!calledFromValidUser()) { - return; - } - synchronized (mMethodMap) { - if (!calledWithValidToken(token)) { - return; - } - } - mWindowManagerInternal.clearLastInputMethodWindowForTransition(); - } - - @BinderThread private void notifyUserAction(@NonNull IBinder token) { if (DEBUG) { Slog.d(TAG, "Got the notification of a user action."); @@ -4957,7 +4940,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub try { synchronized (mMethodMap) { hideCurrentInputLocked(0, null); - unbindCurrentMethodLocked(false); + unbindCurrentMethodLocked(); // Reset the current IME resetSelectedInputMethodAndSubtypeLocked(null); // Also reset the settings of the current IME @@ -5030,12 +5013,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void clearLastInputMethodWindowForTransition() { - mImms.clearLastInputMethodWindowForTransition(mToken); - } - - @BinderThread - @Override public IInputContentUriToken createInputContentUriToken(Uri contentUri, String packageName) { return mImms.createInputContentUriToken(mToken, contentUri, packageName); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 67c5e0034bf6..cade07cfb821 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -4284,8 +4284,12 @@ public class NotificationManagerService extends SystemService { @VisibleForTesting int resolveNotificationUid(String callingPkg, String targetPkg, int callingUid, int userId) { + if (userId == UserHandle.USER_ALL) { + userId = USER_SYSTEM; + } // posted from app A on behalf of app A - if (isCallerSameApp(targetPkg, callingUid) && TextUtils.equals(callingPkg, targetPkg)) { + if (isCallerSameApp(targetPkg, callingUid, userId) + && TextUtils.equals(callingPkg, targetPkg)) { return callingUid; } @@ -4322,7 +4326,7 @@ public class NotificationManagerService extends SystemService { if (!isSystemNotification && !isNotificationFromListener) { synchronized (mNotificationLock) { if (mNotificationsByKey.get(r.sbn.getKey()) == null - && isCallerInstantApp(pkg, callingUid)) { + && isCallerInstantApp(pkg, callingUid, r.getUserId())) { // Ephemeral apps have some special constraints for notifications. // They are not allowed to create new notifications however they are allowed to // update notifications created by the system (e.g. a foreground service @@ -6416,7 +6420,8 @@ public class NotificationManagerService extends SystemService { } } - private boolean isCallerInstantApp(String pkg, int callingUid) { + @VisibleForTesting + boolean isCallerInstantApp(String pkg, int callingUid, int userId) { // System is always allowed to act for ephemeral apps. if (isUidSystemOrPhone(callingUid)) { return false; @@ -6425,8 +6430,7 @@ public class NotificationManagerService extends SystemService { mAppOps.checkPackage(callingUid, pkg); try { - ApplicationInfo ai = mPackageManager.getApplicationInfo(pkg, 0, - UserHandle.getCallingUserId()); + ApplicationInfo ai = mPackageManager.getApplicationInfo(pkg, 0, userId); if (ai == null) { throw new SecurityException("Unknown package " + pkg); } @@ -6438,13 +6442,13 @@ public class NotificationManagerService extends SystemService { } private void checkCallerIsSameApp(String pkg) { - checkCallerIsSameApp(pkg, Binder.getCallingUid()); + checkCallerIsSameApp(pkg, Binder.getCallingUid(), UserHandle.getCallingUserId()); } - private void checkCallerIsSameApp(String pkg, int uid) { + private void checkCallerIsSameApp(String pkg, int uid, int userId) { try { ApplicationInfo ai = mPackageManager.getApplicationInfo( - pkg, 0, UserHandle.getCallingUserId()); + pkg, 0, userId); if (ai == null) { throw new SecurityException("Unknown package " + pkg); } @@ -6466,9 +6470,9 @@ public class NotificationManagerService extends SystemService { } } - private boolean isCallerSameApp(String pkg, int uid) { + private boolean isCallerSameApp(String pkg, int uid, int userId) { try { - checkCallerIsSameApp(pkg, uid); + checkCallerIsSameApp(pkg, uid, userId); return true; } catch (SecurityException e) { return false; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 91af0eca8707..e10827bc6101 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -232,6 +232,7 @@ import android.os.storage.StorageManager; import android.os.storage.StorageManagerInternal; import android.os.storage.VolumeInfo; import android.os.storage.VolumeRecord; +import android.permission.PermissionManager; import android.provider.Settings.Global; import android.provider.Settings.Secure; import android.security.KeyStore; @@ -2912,13 +2913,15 @@ public class PackageManagerService extends IPackageManager.Stub if (mIsUpgrade) { final int callingUid = getCallingUid(); - final int numSplitPerms = PackageParser.SPLIT_PERMISSIONS.length; + final List<PermissionManager.SplitPermissionInfo> splitPermissions = + mContext.getSystemService(PermissionManager.class).getSplitPermissions(); + final int numSplitPerms = splitPermissions.size(); for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) { - final PackageParser.SplitPermissionInfo splitPerm = - PackageParser.SPLIT_PERMISSIONS[splitPermNum]; - final String rootPerm = splitPerm.rootPerm; + final PermissionManager.SplitPermissionInfo splitPerm = + splitPermissions.get(splitPermNum); + final String rootPerm = splitPerm.getRootPermission(); - if (preUpgradeSdkVersion >= splitPerm.targetSdk) { + if (preUpgradeSdkVersion >= splitPerm.getTargetSdk()) { continue; } @@ -2926,7 +2929,7 @@ public class PackageManagerService extends IPackageManager.Stub for (int packageNum = 0; packageNum < numPackages; packageNum++) { final PackageParser.Package pkg = mPackages.valueAt(packageNum); - if (pkg.applicationInfo.targetSdkVersion >= splitPerm.targetSdk + if (pkg.applicationInfo.targetSdkVersion >= splitPerm.getTargetSdk() || !pkg.requestedPermissions.contains(rootPerm)) { continue; } @@ -2938,7 +2941,7 @@ public class PackageManagerService extends IPackageManager.Stub continue; } - final String[] newPerms = splitPerm.newPerms; + final String[] newPerms = splitPerm.getNewPermissions(); final int numNewPerms = newPerms.length; for (int newPermNum = 0; newPermNum < numNewPerms; newPermNum++) { @@ -15336,7 +15339,9 @@ public class PackageManagerService extends IPackageManager.Stub // This needs to be done before invoking dexopt so that any install-time profile // can be used for optimizations. mArtManagerService.prepareAppProfiles( - pkg, resolveUserIds(reconciledPkg.installForUser.getIdentifier())); + pkg, + resolveUserIds(reconciledPkg.installForUser.getIdentifier()), + /* updateReferenceProfileContent= */ true); // Check whether we need to dexopt the app. // @@ -21184,8 +21189,18 @@ public class PackageManagerService extends IPackageManager.Stub // // We also have to cover non system users because we do not call the usual install package // methods for them. + // + // NOTE: in order to speed up first boot time we only create the current profile and do not + // update the content of the reference profile. A system image should already be configured + // with the right profile keys and the profiles for the speed-profile prebuilds should + // already be copied. That's done in #performDexOptUpgrade. + // + // TODO(calin, mathieuc): We should use .dm files for prebuilds profiles instead of + // manually copying them in #performDexOptUpgrade. When we do that we should have a more + // granular check here and only update the existing profiles. if (mIsUpgrade || mFirstBoot || (userId != UserHandle.USER_SYSTEM)) { - mArtManagerService.prepareAppProfiles(pkg, userId); + mArtManagerService.prepareAppProfiles(pkg, userId, + /* updateReferenceProfileContent= */ false); } if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) { diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java index 21daa39e05e0..910ea738acd8 100644 --- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java +++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java @@ -389,7 +389,8 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { * - create the current primary profile to save time at app startup time. * - copy the profiles from the associated dex metadata file to the reference profile. */ - public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user) { + public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user, + boolean updateReferenceProfileContent) { final int appId = UserHandle.getAppId(pkg.applicationInfo.uid); if (user < 0) { Slog.wtf(TAG, "Invalid user id: " + user); @@ -404,8 +405,14 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) { String codePath = codePathsProfileNames.keyAt(i); String profileName = codePathsProfileNames.valueAt(i); - File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath)); - String dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath(); + String dexMetadataPath = null; + // Passing the dex metadata file to the prepare method will update the reference + // profile content. As such, we look for the dex metadata file only if we need to + // perform an update. + if (updateReferenceProfileContent) { + File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath)); + dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath(); + } synchronized (mInstaller) { boolean result = mInstaller.prepareAppProfile(pkg.packageName, user, appId, profileName, codePath, dexMetadataPath); @@ -423,9 +430,10 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { /** * Prepares the app profiles for a set of users. {@see ArtManagerService#prepareAppProfiles}. */ - public void prepareAppProfiles(PackageParser.Package pkg, int[] user) { + public void prepareAppProfiles(PackageParser.Package pkg, int[] user, + boolean updateReferenceProfileContent) { for (int i = 0; i < user.length; i++) { - prepareAppProfiles(pkg, user[i]); + prepareAppProfiles(pkg, user[i], updateReferenceProfileContent); } } 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 3c9dd636e12b..6f644dd70339 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -34,7 +34,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManagerInternal.PackagesProvider; import android.content.pm.PackageManagerInternal.SyncAdapterPackagesProvider; -import android.content.pm.PackageParser; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.media.RingtoneManager; @@ -48,6 +47,7 @@ import android.os.Message; import android.os.SystemProperties; import android.os.UserHandle; import android.os.storage.StorageManager; +import android.permission.PermissionManager; import android.print.PrintManager; import android.provider.CalendarContract; import android.provider.ContactsContract; @@ -1024,15 +1024,17 @@ public final class DefaultPermissionGrantPolicy { ApplicationInfo applicationInfo = pkg.applicationInfo; // Automatically attempt to grant split permissions to older APKs - final int numSplitPerms = PackageParser.SPLIT_PERMISSIONS.length; + final List<PermissionManager.SplitPermissionInfo> splitPermissions = + mContext.getSystemService(PermissionManager.class).getSplitPermissions(); + final int numSplitPerms = splitPermissions.size(); for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) { - final PackageParser.SplitPermissionInfo splitPerm = - PackageParser.SPLIT_PERMISSIONS[splitPermNum]; + final PermissionManager.SplitPermissionInfo splitPerm = + splitPermissions.get(splitPermNum); if (applicationInfo != null - && applicationInfo.targetSdkVersion < splitPerm.targetSdk - && permissionsWithoutSplits.contains(splitPerm.rootPerm)) { - Collections.addAll(permissions, splitPerm.newPerms); + && applicationInfo.targetSdkVersion < splitPerm.getTargetSdk() + && permissionsWithoutSplits.contains(splitPerm.getRootPermission())) { + Collections.addAll(permissions, splitPerm.getNewPermissions()); } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 91fd8d0e960d..deeae26d034c 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -533,8 +533,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { GlobalActions mGlobalActions; Handler mHandler; - WindowState mLastInputMethodWindow = null; - WindowState mLastInputMethodTargetWindow = null; // FIXME This state is shared between the input reader and handler thread. // Technically it's broken and buggy but it has been like this for many years @@ -4732,12 +4730,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } final WindowManager.LayoutParams attrs = win.getAttrs(); final boolean isDefaultDisplay = win.isDefaultDisplay(); - final boolean needsToOffsetInputMethodTarget = - (win == mLastInputMethodTargetWindow) && (mLastInputMethodWindow != null); - if (needsToOffsetInputMethodTarget) { - if (DEBUG_LAYOUT) Slog.i(TAG, "Offset ime target window by the last ime window state"); - offsetInputMethodWindowLw(mLastInputMethodWindow, displayFrames); - } final int type = attrs.type; final int fl = PolicyControl.getWindowFlags(win, attrs); @@ -5191,7 +5183,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { // can't appear underneath them. if (type == TYPE_INPUT_METHOD && win.isVisibleLw() && !win.getGivenInsetsPendingLw()) { - setLastInputMethodWindowLw(null, null); offsetInputMethodWindowLw(win, displayFrames); } if (type == TYPE_VOICE_INTERACTION && win.isVisibleLw() @@ -7822,12 +7813,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - public void setLastInputMethodWindowLw(WindowState ime, WindowState target) { - mLastInputMethodWindow = ime; - mLastInputMethodTargetWindow = target; - } - - @Override public void setDismissImeOnBackKeyPressed(boolean newValue) { mDismissImeOnBackKeyPressed = newValue; } @@ -7845,7 +7830,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (statusBar != null) { statusBar.setCurrentUser(newUserId); } - setLastInputMethodWindowLw(null, null); } @Override @@ -8020,14 +8004,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { pw.print(prefix); pw.print("mShowingDream="); pw.print(mShowingDream); pw.print(" mDreamingLockscreen="); pw.print(mDreamingLockscreen); pw.print(" mDreamingSleepToken="); pw.println(mDreamingSleepToken); - if (mLastInputMethodWindow != null) { - pw.print(prefix); pw.print("mLastInputMethodWindow="); - pw.println(mLastInputMethodWindow); - } - if (mLastInputMethodTargetWindow != null) { - pw.print(prefix); pw.print("mLastInputMethodTargetWindow="); - pw.println(mLastInputMethodTargetWindow); - } if (mStatusBar != null) { pw.print(prefix); pw.print("mStatusBar="); pw.print(mStatusBar); pw.print(" isStatusBarKeyguard="); diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 1fcdd63c4488..27ab3efb3fb1 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -1536,13 +1536,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { public void lockNow(Bundle options); /** - * Set the last used input method window state. This state is used to make IME transition - * smooth. - * @hide - */ - public void setLastInputMethodWindowLw(WindowState ime, WindowState target); - - /** * An internal callback (from InputMethodManagerService) to notify a state change regarding * whether the back key should dismiss the software keyboard (IME) or not. * diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index bfa03ca9f2be..c6e6449313c0 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -966,6 +966,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { e.writeLong(processMemoryState.rssInBytes); e.writeLong(processMemoryState.cacheInBytes); e.writeLong(processMemoryState.swapInBytes); + e.writeLong(processMemoryState.rssHighWatermarkInBytes); pulledData.add(e); } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index eaaf804d2cf9..236982fd76ac 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2652,6 +2652,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ void setInputMethodWindowLocked(WindowState win) { mInputMethodWindow = win; + // Update display configuration for IME process. + if (mInputMethodWindow != null) { + final int imePid = mInputMethodWindow.mSession.mPid; + mService.mAtmInternal.onImeWindowSetOnDisplay(imePid, + mInputMethodWindow.getDisplayId()); + } computeImeTarget(true /* updateImeTarget */); } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 27b623bc0ec3..5410676a49fc 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -340,20 +340,6 @@ public abstract class WindowManagerInternal { public abstract int getInputMethodWindowVisibleHeight(int displayId); /** - * Saves last input method window for transition. - * - * Note that it is assumed that this method is called only by InputMethodManagerService. - */ - public abstract void saveLastInputMethodWindowForTransition(); - - /** - * Clears last input method window for transition. - * - * Note that it is assumed that this method is called only by InputMethodManagerService. - */ - public abstract void clearLastInputMethodWindowForTransition(); - - /** * Notifies WindowManagerService that the current IME window status is being changed. * * <p>Only {@link com.android.server.inputmethod.InputMethodManagerService} is the expected and diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 017f667756bc..7caa7aedb873 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1414,7 +1414,6 @@ public class WindowManagerService extends IWindowManager.Stub win.mToken.addWindow(win); if (type == TYPE_INPUT_METHOD) { - win.mGivenInsetsPending = true; displayContent.setInputMethodWindowLocked(win); imMayMove = false; } else if (type == TYPE_INPUT_METHOD_DIALOG) { @@ -5670,11 +5669,6 @@ public class WindowManagerService extends IWindowManager.Stub mInputManagerCallback.freezeInputDispatchingLw(); - // Clear the last input window -- that is just used for - // clean transitions between IMEs, and if we are freezing - // the screen then the whole world is changing behind the scenes. - mPolicy.setLastInputMethodWindowLw(null, null); - if (mAppTransition.isTransitionSet()) { mAppTransition.freeze(); } @@ -7287,23 +7281,6 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void saveLastInputMethodWindowForTransition() { - synchronized (mWindowMap) { - final WindowState imeWindow = mRoot.getCurrentInputMethodWindow(); - if (imeWindow != null) { - mPolicy.setLastInputMethodWindowLw(imeWindow, mInputMethodTarget); - } - } - } - - @Override - public void clearLastInputMethodWindowForTransition() { - synchronized (mWindowMap) { - mPolicy.setLastInputMethodWindowLw(null, null); - } - } - - @Override public void updateInputMethodWindowStatus(@NonNull IBinder imeToken, boolean imeWindowVisible, boolean dismissImeOnBackKeyPressed) { mPolicy.setDismissImeOnBackKeyPressed(dismissImeOnBackKeyPressed); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 7161a70b08fe..f7c6d77b4163 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -4535,7 +4535,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP updateSurfacePosition(getPendingTransaction()); } - private void updateSurfacePosition(Transaction t) { + @VisibleForTesting + void updateSurfacePosition(Transaction t) { if (mSurfaceControl == null) { return; } diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java index 57ebbfcf8dd6..de915ab0829c 100644 --- a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -122,6 +122,13 @@ public class BackupManagerServiceTest { ShadowAppBackupUtils.reset(); } + @Test + public void testMoreDebug_isFalse() throws Exception { + boolean moreDebug = BackupManagerService.MORE_DEBUG; + + assertThat(moreDebug).isFalse(); + } + /* Tests for destination string */ @Test diff --git a/services/robotests/src/com/android/server/backup/keyvalue/AgentExceptionTest.java b/services/robotests/src/com/android/server/backup/keyvalue/AgentExceptionTest.java new file mode 100644 index 000000000000..373033500cde --- /dev/null +++ b/services/robotests/src/com/android/server/backup/keyvalue/AgentExceptionTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.keyvalue; + +import static com.google.common.truth.Truth.assertThat; + +import android.platform.test.annotations.Presubmit; + +import com.android.server.testing.FrameworkRobolectricTestRunner; +import com.android.server.testing.SystemLoaderPackages; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +import java.io.IOException; + +@RunWith(FrameworkRobolectricTestRunner.class) +@Config(manifest = Config.NONE, sdk = 26) +@SystemLoaderPackages({"com.android.server.backup"}) +@Presubmit +public class AgentExceptionTest { + @Test + public void testTransitory_isTransitory() throws Exception { + AgentException exception = AgentException.transitory(); + + assertThat(exception.isTransitory()).isTrue(); + } + + @Test + public void testTransitory_withCause() throws Exception { + Exception cause = new IOException(); + + AgentException exception = AgentException.transitory(cause); + + assertThat(exception.isTransitory()).isTrue(); + assertThat(exception.getCause()).isEqualTo(cause); + } + + @Test + public void testPermanent_isNotTransitory() throws Exception { + AgentException exception = AgentException.permanent(); + + assertThat(exception.isTransitory()).isFalse(); + } + + @Test + public void testPermanent_withCause() throws Exception { + Exception cause = new IOException(); + + AgentException exception = AgentException.permanent(cause); + + assertThat(exception.isTransitory()).isFalse(); + assertThat(exception.getCause()).isEqualTo(cause); + } +} diff --git a/services/robotests/src/com/android/server/backup/keyvalue/BackupExceptionTest.java b/services/robotests/src/com/android/server/backup/keyvalue/BackupExceptionTest.java new file mode 100644 index 000000000000..5ea74f163bd6 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/keyvalue/BackupExceptionTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.keyvalue; + +import static com.google.common.truth.Truth.assertThat; + +import android.platform.test.annotations.Presubmit; + +import com.android.server.testing.FrameworkRobolectricTestRunner; +import com.android.server.testing.SystemLoaderPackages; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +import java.io.IOException; + +@RunWith(FrameworkRobolectricTestRunner.class) +@Config(manifest = Config.NONE, sdk = 26) +@SystemLoaderPackages({"com.android.server.backup"}) +@Presubmit +public class BackupExceptionTest { + @Test + public void testConstructor_passesCause() { + Exception cause = new IOException(); + + Exception exception = new BackupException(cause); + + assertThat(exception.getCause()).isEqualTo(cause); + } +} diff --git a/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java b/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java index 21b90f1c2281..31e8333fea89 100644 --- a/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java +++ b/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java @@ -62,6 +62,13 @@ public class KeyValueBackupReporterTest { } @Test + public void testMoreDebug_isFalse() throws Exception { + boolean moreDebug = KeyValueBackupReporter.MORE_DEBUG; + + assertThat(moreDebug).isFalse(); + } + + @Test public void testOnNewThread_logsCorrectly() throws Exception { KeyValueBackupReporter.onNewThread("foo"); @@ -81,39 +88,4 @@ public class KeyValueBackupReporterTest { assertThat(observer).isEqualTo(mObserver); } - - @Test - public void testOnRevertTask_logsCorrectly() throws Exception { - setMoreDebug(true); - - mReporter.onRevertTask(); - - assertLogcat(TAG, Log.INFO); - } - - @Test - public void testOnRemoteCallReturned_logsCorrectly() throws Exception { - setMoreDebug(true); - - mReporter.onRemoteCallReturned(RemoteResult.of(3), "onFoo()"); - - assertLogcat(TAG, Log.VERBOSE); - ShadowLog.LogItem log = ShadowLog.getLogsForTag(TAG).get(0); - assertThat(log.msg).contains("onFoo()"); - assertThat(log.msg).contains("3"); - } - - /** - * HACK: We actually want {@link KeyValueBackupReporter#MORE_DEBUG} to be a constant to be able - * to strip those lines at build time. So, we have to do this to test :( - */ - private static void setMoreDebug(boolean value) - throws NoSuchFieldException, IllegalAccessException { - if (KeyValueBackupReporter.MORE_DEBUG == value) { - return; - } - Field moreDebugField = KeyValueBackupReporter.class.getDeclaredField("MORE_DEBUG"); - moreDebugField.setAccessible(true); - moreDebugField.set(null, value); - } } diff --git a/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index b4bc9d199cb0..fb57d68082a2 100644 --- a/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -155,9 +155,7 @@ import java.util.List; import java.util.concurrent.TimeoutException; import java.util.stream.Stream; -// TODO: When returning to RUNNING_QUEUE vs FINAL, RUNNING_QUEUE sets status = OK. Why? Verify? -// TODO: Check queue in general, behavior w/ multiple packages -// TODO: Test PM invocation +// TODO: Test agents timing out @RunWith(FrameworkRobolectricTestRunner.class) @Config( manifest = Config.NONE, @@ -370,6 +368,47 @@ public class KeyValueBackupTaskTest { } @Test + public void testRunTask_whenOnePackage_cleansUpPmFiles() throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + setUpAgent(PACKAGE_1); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); + + runTask(task); + + assertCleansUpFiles(mTransport, PM_PACKAGE); + } + + @Test + public void testRunTask_whenTransportReturnsTransportErrorForPm_cleansUpPmFiles() + throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + when(transportMock.transport.performBackup( + argThat(packageInfo(PM_PACKAGE)), any(), anyInt())) + .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED); + setUpAgent(PACKAGE_1); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); + + runTask(task); + + assertCleansUpFiles(mTransport, PM_PACKAGE); + } + + @Test + public void testRunTask_whenTransportReturnsTransportErrorForPm_resetsBackupState() + throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + when(transportMock.transport.performBackup( + argThat(packageInfo(PM_PACKAGE)), any(), anyInt())) + .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED); + setUpAgent(PACKAGE_1); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); + + runTask(task); + + verify(mBackupManagerService).resetBackupState(getStateDirectory(mTransport).toFile()); + } + + @Test public void testRunTask_whenOnePackage_updatesBookkeeping() throws Exception { // Transport has to be initialized to not reset current token TransportMock transportMock = setUpInitializedTransport(mTransport); @@ -418,7 +457,7 @@ public class KeyValueBackupTaskTest { public void testRunTask_whenNonPmPackageAndNonIncremental_doesNotBackUpPm() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); setUpAgentWithData(PACKAGE_1); - PackageManagerBackupAgent pmAgent = spy(createPmAgent()); + BackupAgent pmAgent = spy(createPmAgent()); when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent)); KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1); @@ -431,7 +470,7 @@ public class KeyValueBackupTaskTest { public void testRunTask_whenNonPmPackageAndPmAndNonIncremental_backsUpPm() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); setUpAgentWithData(PACKAGE_1); - PackageManagerBackupAgent pmAgent = spy(createPmAgent()); + BackupAgent pmAgent = spy(createPmAgent()); when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent)); KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1, PM_PACKAGE); @@ -445,7 +484,7 @@ public class KeyValueBackupTaskTest { public void testRunTask_whenNonPmPackageAndIncremental_backsUpPm() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); setUpAgentWithData(PACKAGE_1); - PackageManagerBackupAgent pmAgent = spy(createPmAgent()); + BackupAgent pmAgent = spy(createPmAgent()); when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent)); KeyValueBackupTask task = createKeyValueBackupTask(transportMock, false, PACKAGE_1); @@ -529,6 +568,35 @@ public class KeyValueBackupTaskTest { } @Test + public void testRunTask_whenPackageUnknown() throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + // Not calling setUpAgent() for PACKAGE_1 + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); + + runTask(task); + + verify(transportMock.transport, never()) + .performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt()); + verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_PACKAGE_NOT_FOUND); + verify(mObserver).backupFinished(SUCCESS); + assertBackupNotPendingFor(PACKAGE_1); + } + + @Test + public void testRunTask_whenFirstPackageUnknown_callsTransportForSecondPackage() + throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + // Not calling setUpAgent() for PACKAGE_1 + setUpAgentWithData(PACKAGE_2); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2); + + runTask(task); + + verify(transportMock.transport) + .performBackup(argThat(packageInfo(PACKAGE_2)), any(), anyInt()); + } + + @Test public void testRunTask_whenPackageNotEligibleForBackup() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); AgentMock agentMock = setUpAgentWithData(PACKAGE_1.backupNotAllowed()); @@ -545,6 +613,19 @@ public class KeyValueBackupTaskTest { } @Test + public void testRunTask_whenFirstPackageNotEligibleForBackup_callsTransportForSecondPackage() + throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + setUpAgentsWithData(PACKAGE_1.backupNotAllowed(), PACKAGE_2); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2); + + runTask(task); + + verify(transportMock.transport) + .performBackup(argThat(packageInfo(PACKAGE_2)), any(), anyInt()); + } + + @Test public void testRunTask_whenPackageDoesFullBackup() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); PackageData packageData = fullBackupPackage(1); @@ -561,6 +642,20 @@ public class KeyValueBackupTaskTest { } @Test + public void testRunTask_whenFirstPackageDoesFullBackup_callsTransportForSecondPackage() + throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + PackageData packageData = fullBackupPackage(1); + setUpAgentsWithData(packageData, PACKAGE_2); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, packageData, PACKAGE_2); + + runTask(task); + + verify(transportMock.transport) + .performBackup(argThat(packageInfo(PACKAGE_2)), any(), anyInt()); + } + + @Test public void testRunTask_whenPackageIsStopped() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); AgentMock agentMock = setUpAgentWithData(PACKAGE_1.stopped()); @@ -575,18 +670,16 @@ public class KeyValueBackupTaskTest { } @Test - public void testRunTask_whenPackageUnknown() throws Exception { + public void testRunTask_whenFirstPackageIsStopped_callsTransportForSecondPackage() + throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); - // Not calling setUpAgent() - KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); + setUpAgentsWithData(PACKAGE_1.stopped(), PACKAGE_2); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2); runTask(task); - verify(transportMock.transport, never()) - .performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt()); - verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_PACKAGE_NOT_FOUND); - verify(mObserver).backupFinished(SUCCESS); - assertBackupNotPendingFor(PACKAGE_1); + verify(transportMock.transport) + .performBackup(argThat(packageInfo(PACKAGE_2)), any(), anyInt()); } @Test @@ -629,6 +722,7 @@ public class KeyValueBackupTaskTest { verify(mBackupManagerService).setWorkSource(null); verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_AGENT_FAILURE); verify(mObserver).backupFinished(BackupManager.SUCCESS); + assertBackupPendingFor(PACKAGE_1); } @Test @@ -645,6 +739,7 @@ public class KeyValueBackupTaskTest { verify(mBackupManagerService).setWorkSource(null); verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_AGENT_FAILURE); verify(mObserver).backupFinished(BackupManager.SUCCESS); + assertBackupPendingFor(PACKAGE_1); } @Test @@ -798,7 +893,7 @@ public class KeyValueBackupTaskTest { runTask(task); - assertBackupNotPendingFor(PACKAGE_1); + assertBackupPendingFor(PACKAGE_1); } @Test @@ -1140,6 +1235,38 @@ public class KeyValueBackupTaskTest { } @Test + public void testRunTask_whenPmAgentWritesData_callsTransportPerformBackupWithAgentData() + throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + setUpAgent(PACKAGE_1); + Path backupDataPath = createTemporaryFile(); + when(transportMock.transport.performBackup( + argThat(packageInfo(PM_PACKAGE)), any(), anyInt())) + .then(copyBackupDataTo(backupDataPath)); + BackupAgent pmAgent = spy(createPmAgent()); + when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent)); + agentOnBackupDo( + pmAgent, + (oldState, dataOutput, newState) -> { + writeData(dataOutput, "key1", "data1".getBytes()); + writeData(dataOutput, "key2", "data2".getBytes()); + writeState(newState, "newState".getBytes()); + }); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); + + runTask(task); + + verify(transportMock.transport) + .performBackup(argThat(packageInfo(PM_PACKAGE)), any(), anyInt()); + try (FileInputStream inputStream = new FileInputStream(backupDataPath.toFile())) { + BackupDataInput backupData = new BackupDataInput(inputStream.getFD()); + assertDataHasKeyValue(backupData, "key1", "data1".getBytes()); + assertDataHasKeyValue(backupData, "key2", "data2".getBytes()); + assertThat(backupData.readNextHeader()).isFalse(); + } + } + + @Test public void testRunTask_whenPerformBackupSucceeds_callsTransportFinishBackup() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); @@ -1176,6 +1303,50 @@ public class KeyValueBackupTaskTest { } @Test + public void testRunTask_whenFinishBackupSucceedsForPm_cleansUp() throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + setUpAgent(PACKAGE_1); + when(transportMock.transport.finishBackup()).thenReturn(BackupTransport.TRANSPORT_OK); + BackupAgent pmAgent = spy(createPmAgent()); + when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent)); + agentOnBackupDo( + pmAgent, + (oldState, dataOutput, newState) -> { + writeData(dataOutput, "key", "data".getBytes()); + writeState(newState, "newState".getBytes()); + }); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); + + runTask(task); + + assertThat(Files.readAllBytes(getStateFile(mTransport, PM_PACKAGE))) + .isEqualTo("newState".getBytes()); + assertCleansUpFiles(mTransport, PM_PACKAGE); + // We don't unbind PM + verify(mBackupManagerService, never()).unbindAgent(argThat(applicationInfo(PM_PACKAGE))); + } + + @Test + public void testRunTask_whenFinishBackupSucceedsForPm_doesNotUnbindPm() throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + setUpAgent(PACKAGE_1); + when(transportMock.transport.finishBackup()).thenReturn(BackupTransport.TRANSPORT_OK); + BackupAgent pmAgent = spy(createPmAgent()); + when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent)); + agentOnBackupDo( + pmAgent, + (oldState, dataOutput, newState) -> { + writeData(dataOutput, "key", "data".getBytes()); + writeState(newState, "newState".getBytes()); + }); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); + + runTask(task); + + verify(mBackupManagerService, never()).unbindAgent(argThat(applicationInfo(PM_PACKAGE))); + } + + @Test public void testRunTask_whenFinishBackupSucceeds_logsBackupPackageEvent() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); setUpAgentWithData(PACKAGE_1); @@ -1354,6 +1525,7 @@ public class KeyValueBackupTaskTest { public void testRunTask_whenTransportReturnsQuotaExceeded_updatesBookkeeping() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); + setUpAgentWithData(PACKAGE_1); when(transportMock.transport.performBackup( argThat(packageInfo(PACKAGE_1)), any(), anyInt())) .thenReturn(BackupTransport.TRANSPORT_QUOTA_EXCEEDED); @@ -1701,9 +1873,9 @@ public class KeyValueBackupTaskTest { } @Test - public void testRunTask_whenPmAgentFails() throws Exception { + public void testRunTask_whenPmAgentFails_reportsCorrectly() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); - PackageManagerBackupAgent pmAgent = createThrowingPmAgent(new RuntimeException()); + BackupAgent pmAgent = createThrowingPmAgent(new RuntimeException()); when(mBackupManagerService.makeMetadataAgent()).thenReturn(pmAgent); KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); @@ -1718,6 +1890,75 @@ public class KeyValueBackupTaskTest { } @Test + public void testRunTask_whenPmAgentFails_revertsTask() throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + setUpAgent(PACKAGE_1); + BackupAgent pmAgent = createThrowingPmAgent(new RuntimeException()); + when(mBackupManagerService.makeMetadataAgent()).thenReturn(pmAgent); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); + + runTask(task); + + assertTaskReverted(transportMock, PACKAGE_1); + } + + @Test + public void testRunTask_whenPmAgentFails_cleansUpFiles() throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + setUpAgent(PACKAGE_1); + BackupAgent pmAgent = createThrowingPmAgent(new RuntimeException()); + when(mBackupManagerService.makeMetadataAgent()).thenReturn(pmAgent); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); + + runTask(task); + + assertCleansUpFiles(mTransport, PM_PACKAGE); + } + + @Test + public void testRunTask_whenPmAgentFails_resetsBackupState() throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + setUpAgent(PACKAGE_1); + BackupAgent pmAgent = createThrowingPmAgent(new RuntimeException()); + when(mBackupManagerService.makeMetadataAgent()).thenReturn(pmAgent); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); + + runTask(task); + + verify(mBackupManagerService).resetBackupState(getStateDirectory(mTransport).toFile()); + } + + @Test + public void testRunTask_whenMarkCancelDuringPmOnBackup_resetsBackupState() throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + setUpAgent(PACKAGE_1); + BackupAgent pmAgent = spy(createPmAgent()); + when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent)); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); + agentOnBackupDo( + pmAgent, (oldState, dataOutput, newState) -> runInWorkerThread(task::markCancel)); + + runTask(task); + + verify(mBackupManagerService).resetBackupState(getStateDirectory(mTransport).toFile()); + } + + @Test + public void testRunTask_whenMarkCancelDuringPmOnBackup_cleansUpFiles() throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + setUpAgent(PACKAGE_1); + BackupAgent pmAgent = spy(createPmAgent()); + when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent)); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); + agentOnBackupDo( + pmAgent, (oldState, dataOutput, newState) -> runInWorkerThread(task::markCancel)); + + runTask(task); + + assertCleansUpFiles(mTransport, PM_PACKAGE); + } + + @Test public void testRunTask_whenBackupRunning_doesNotThrow() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); when(mBackupManagerService.isBackupOperationInProgress()).thenReturn(true); @@ -1736,7 +1977,7 @@ public class KeyValueBackupTaskTest { runTask(task); - verify(mReporter).onReadAgentDataError(eq(PACKAGE_1.packageName), any()); + verify(mReporter).onAgentDataError(eq(PACKAGE_1.packageName), any()); } @Test @@ -1779,6 +2020,24 @@ public class KeyValueBackupTaskTest { } @Test + public void testRunTask_whenMarkCancelDuringAgentOnBackup_cleansUpFiles() throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + AgentMock agentMock = setUpAgent(PACKAGE_1); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); + agentOnBackupDo( + agentMock, + (oldState, dataOutput, newState) -> { + writeData(dataOutput, "key", "data".getBytes()); + writeState(newState, "newState".getBytes()); + runInWorkerThread(task::markCancel); + }); + + runTask(task); + + assertCleansUpFiles(mTransport, PACKAGE_1); + } + + @Test public void testRunTask_whenMarkCancelDuringFirstAgentOnBackup_doesNotCallTransportAfterWaitCancel() throws Exception { @@ -2293,20 +2552,28 @@ public class KeyValueBackupTaskTest { */ private static void agentOnBackupDo(AgentMock agentMock, BackupAgentOnBackup function) throws Exception { - doAnswer( - (BackupAgentOnBackup) - (oldState, dataOutput, newState) -> { - ByteArrayOutputStream outputStream = - new ByteArrayOutputStream(); - transferStreamedData( - new FileInputStream(oldState.getFileDescriptor()), - outputStream); - agentMock.oldState = outputStream.toByteArray(); - agentMock.oldStateHistory.add(agentMock.oldState); - function.onBackup(oldState, dataOutput, newState); - }) - .when(agentMock.agent) - .onBackup(any(), any(), any()); + agentOnBackupDo( + agentMock.agent, + (oldState, dataOutput, newState) -> { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + transferStreamedData( + new FileInputStream(oldState.getFileDescriptor()), outputStream); + agentMock.oldState = outputStream.toByteArray(); + agentMock.oldStateHistory.add(agentMock.oldState); + function.onBackup(oldState, dataOutput, newState); + }); + } + + /** + * Implements {@code function} for {@link BackupAgent#onBackup(ParcelFileDescriptor, + * BackupDataOutput, ParcelFileDescriptor)} of {@code agentMock}. + * + * @see #agentOnBackupDo(AgentMock, BackupAgentOnBackup) + * @see #remoteAgentOnBackupThrows(AgentMock, BackupAgentOnBackup) + */ + private static void agentOnBackupDo(BackupAgent backupAgent, BackupAgentOnBackup function) + throws IOException { + doAnswer(function).when(backupAgent).onBackup(any(), any(), any()); } /** @@ -2400,6 +2667,10 @@ public class KeyValueBackupTaskTest { // constructor assertJournalDoesNotContain(mBackupManagerService.getJournal(), packageName); assertThat(mBackupManagerService.getPendingBackups()).doesNotContainKey(packageName); + // Also verifying BMS is never called since for some cases the package wouldn't be + // pending for other reasons (for example it's not eligible for backup). Regardless of + // these reasons, we shouldn't mark them as pending backup (call dataChangedImpl()). + verify(mBackupManagerService, never()).dataChangedImpl(packageName); } } diff --git a/services/robotests/src/com/android/server/backup/keyvalue/TaskExceptionTest.java b/services/robotests/src/com/android/server/backup/keyvalue/TaskExceptionTest.java new file mode 100644 index 000000000000..4b79657b1dae --- /dev/null +++ b/services/robotests/src/com/android/server/backup/keyvalue/TaskExceptionTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.keyvalue; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.expectThrows; + +import android.app.backup.BackupTransport; +import android.platform.test.annotations.Presubmit; + +import com.android.server.testing.FrameworkRobolectricTestRunner; +import com.android.server.testing.SystemLoaderPackages; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +import java.io.IOException; + +@RunWith(FrameworkRobolectricTestRunner.class) +@Config(manifest = Config.NONE, sdk = 26) +@SystemLoaderPackages({"com.android.server.backup"}) +@Presubmit +public class TaskExceptionTest { + @Test + public void testStateCompromised() { + TaskException exception = TaskException.stateCompromised(); + + assertThat(exception.isStateCompromised()).isTrue(); + assertThat(exception.getStatus()).isEqualTo(BackupTransport.TRANSPORT_ERROR); + } + + @Test + public void testStateCompromised_whenCauseInstanceOfTaskException() { + Exception cause = TaskException.forStatus(BackupTransport.TRANSPORT_NOT_INITIALIZED); + + TaskException exception = TaskException.stateCompromised(cause); + + assertThat(exception.isStateCompromised()).isTrue(); + assertThat(exception.getStatus()).isEqualTo(BackupTransport.TRANSPORT_NOT_INITIALIZED); + assertThat(exception.getCause()).isEqualTo(cause); + } + + @Test + public void testStateCompromised_whenCauseNotInstanceOfTaskException() { + Exception cause = new IOException(); + + TaskException exception = TaskException.stateCompromised(cause); + + assertThat(exception.isStateCompromised()).isTrue(); + assertThat(exception.getStatus()).isEqualTo(BackupTransport.TRANSPORT_ERROR); + assertThat(exception.getCause()).isEqualTo(cause); + } + + @Test + public void testForStatus_whenTransportOk_throws() { + expectThrows( + IllegalArgumentException.class, + () -> TaskException.forStatus(BackupTransport.TRANSPORT_OK)); + } + + @Test + public void testForStatus_whenTransportNotInitialized() { + TaskException exception = + TaskException.forStatus(BackupTransport.TRANSPORT_NOT_INITIALIZED); + + assertThat(exception.isStateCompromised()).isFalse(); + assertThat(exception.getStatus()).isEqualTo(BackupTransport.TRANSPORT_NOT_INITIALIZED); + } + + @Test + public void testCausedBy_whenCauseInstanceOfTaskException_returnsCause() { + Exception cause = TaskException.forStatus(BackupTransport.TRANSPORT_NOT_INITIALIZED); + + TaskException exception = TaskException.causedBy(cause); + + assertThat(exception).isEqualTo(cause); + } + + @Test + public void testCausedBy_whenCauseNotInstanceOfTaskException() { + Exception cause = new IOException(); + + TaskException exception = TaskException.causedBy(cause); + + assertThat(exception).isNotEqualTo(cause); + assertThat(exception.isStateCompromised()).isFalse(); + assertThat(exception.getStatus()).isEqualTo(BackupTransport.TRANSPORT_ERROR); + assertThat(exception.getCause()).isEqualTo(cause); + } + + @Test + public void testCreate() { + TaskException exception = TaskException.create(); + + assertThat(exception.isStateCompromised()).isFalse(); + assertThat(exception.getStatus()).isEqualTo(BackupTransport.TRANSPORT_ERROR); + } + + @Test + public void testIsStateCompromised_whenStateCompromised_returnsTrue() { + TaskException taskException = TaskException.stateCompromised(); + + boolean stateCompromised = taskException.isStateCompromised(); + + assertThat(stateCompromised).isTrue(); + } + + @Test + public void testIsStateCompromised_whenCreatedWithCreate_returnsFalse() { + TaskException taskException = TaskException.create(); + + boolean stateCompromised = taskException.isStateCompromised(); + + assertThat(stateCompromised).isFalse(); + } + + @Test + public void testGetStatus_whenStatusIsTransportPackageRejected() { + TaskException taskException = + TaskException.forStatus(BackupTransport.TRANSPORT_PACKAGE_REJECTED); + + int status = taskException.getStatus(); + + assertThat(status).isEqualTo(BackupTransport.TRANSPORT_PACKAGE_REJECTED); + } + + @Test + public void testGetStatus_whenStatusIsTransportNotInitialized() { + TaskException taskException = + TaskException.forStatus(BackupTransport.TRANSPORT_NOT_INITIALIZED); + + int status = taskException.getStatus(); + + assertThat(status).isEqualTo(BackupTransport.TRANSPORT_NOT_INITIALIZED); + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java index 06c74371eb51..e8a824a12300 100644 --- a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java +++ b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java @@ -16,9 +16,12 @@ package com.android.server.am; +import static com.android.server.am.MemoryStatUtil.BYTES_IN_KILOBYTE; import static com.android.server.am.MemoryStatUtil.MemoryStat; +import static com.android.server.am.MemoryStatUtil.parseMemoryMaxUsageFromMemCg; import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromMemcg; import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromProcfs; +import static com.android.server.am.MemoryStatUtil.parseVmHWMFromProcfs; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -32,7 +35,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class MemoryStatUtilTest { - private String MEMORY_STAT_CONTENTS = String.join( + private static final String MEMORY_STAT_CONTENTS = String.join( "\n", "cache 96", // keep different from total_cache to catch reading wrong value "rss 97", // keep different from total_rss to catch reading wrong value @@ -67,7 +70,7 @@ public class MemoryStatUtilTest { "total_active_file 81920", "total_unevictable 0"); - private String PROC_STAT_CONTENTS = String.join( + private static final String PROC_STAT_CONTENTS = String.join( " ", "1040", "(system_server)", @@ -122,14 +125,61 @@ public class MemoryStatUtilTest { "3198889956", "0"); + private static final String PROC_STATUS_CONTENTS = "Name:\tandroid.youtube\n" + + "State:\tS (sleeping)\n" + + "Tgid:\t12088\n" + + "Pid:\t12088\n" + + "PPid:\t723\n" + + "TracerPid:\t0\n" + + "Uid:\t10083\t10083\t10083\t10083\n" + + "Gid:\t10083\t10083\t10083\t10083\n" + + "Ngid:\t0\n" + + "FDSize:\t128\n" + + "Groups:\t3003 9997 20083 50083 \n" + + "VmPeak:\t 4546844 kB\n" + + "VmSize:\t 4542636 kB\n" + + "VmLck:\t 0 kB\n" + + "VmPin:\t 0 kB\n" + + "VmHWM:\t 137668 kB\n" // RSS high watermark + + "VmRSS:\t 126776 kB\n" + + "RssAnon:\t 37860 kB\n" + + "RssFile:\t 88764 kB\n" + + "RssShmem:\t 152 kB\n" + + "VmData:\t 4125112 kB\n" + + "VmStk:\t 8192 kB\n" + + "VmExe:\t 24 kB\n" + + "VmLib:\t 102432 kB\n" + + "VmPTE:\t 1300 kB\n" + + "VmPMD:\t 36 kB\n" + + "VmSwap:\t 0 kB\n" + + "Threads:\t95\n" + + "SigQ:\t0/13641\n" + + "SigPnd:\t0000000000000000\n" + + "ShdPnd:\t0000000000000000\n" + + "SigBlk:\t0000000000001204\n" + + "SigIgn:\t0000000000000001\n" + + "SigCgt:\t00000006400084f8\n" + + "CapInh:\t0000000000000000\n" + + "CapPrm:\t0000000000000000\n" + + "CapEff:\t0000000000000000\n" + + "CapBnd:\t0000000000000000\n" + + "CapAmb:\t0000000000000000\n" + + "Seccomp:\t2\n" + + "Cpus_allowed:\tff\n" + + "Cpus_allowed_list:\t0-7\n" + + "Mems_allowed:\t1\n" + + "Mems_allowed_list:\t0\n" + + "voluntary_ctxt_switches:\t903\n" + + "nonvoluntary_ctxt_switches:\t104\n"; + @Test public void testParseMemoryStatFromMemcg_parsesCorrectValues() throws Exception { MemoryStat stat = parseMemoryStatFromMemcg(MEMORY_STAT_CONTENTS); - assertEquals(stat.pgfault, 1); - assertEquals(stat.pgmajfault, 2); - assertEquals(stat.rssInBytes, 3); - assertEquals(stat.cacheInBytes, 4); - assertEquals(stat.swapInBytes, 5); + assertEquals(1, stat.pgfault); + assertEquals(2, stat.pgmajfault); + assertEquals(3, stat.rssInBytes); + assertEquals(4, stat.cacheInBytes); + assertEquals(5, stat.swapInBytes); } @Test @@ -142,6 +192,18 @@ public class MemoryStatUtilTest { } @Test + public void testParseMemoryMaxUsageFromMemCg_parsesCorrectValue() { + assertEquals(1234, parseMemoryMaxUsageFromMemCg("1234")); + } + + @Test + public void testParseMemoryMaxUsageFromMemCg_emptyContents() { + assertEquals(0, parseMemoryMaxUsageFromMemCg("")); + + assertEquals(0, parseMemoryMaxUsageFromMemCg(null)); + } + + @Test public void testParseMemoryStatFromProcfs_parsesCorrectValues() throws Exception { MemoryStat stat = parseMemoryStatFromProcfs(PROC_STAT_CONTENTS); assertEquals(1, stat.pgfault); @@ -159,4 +221,16 @@ public class MemoryStatUtilTest { stat = parseMemoryStatFromProcfs(null); assertNull(stat); } + + @Test + public void testParseVmHWMFromProcfs_parsesCorrectValue() { + assertEquals(137668, parseVmHWMFromProcfs(PROC_STATUS_CONTENTS) / BYTES_IN_KILOBYTE); + } + + @Test + public void testParseVmHWMFromProcfs_emptyContents() { + assertEquals(0, parseVmHWMFromProcfs("")); + + assertEquals(0, parseVmHWMFromProcfs(null)); + } } diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java index 5fb89977f905..474e5b75e5d8 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -458,12 +458,6 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public void setLastInputMethodWindowLw(WindowState ime, - WindowState target) { - - } - - @Override public void showRecentApps() { } diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java index 6af3ea763c13..b7cc9ce7a619 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java @@ -76,7 +76,8 @@ import androidx.test.runner.AndroidJUnit4; */ @SmallTest @FlakyTest(bugId = 74078662) -@Presubmit +// TODO(b/116597907): Re-enable this test in postsubmit after the bug is fixed. +// @Presubmit @RunWith(AndroidJUnit4.class) public class WindowStateTests extends WindowTestsBase { @@ -369,22 +370,31 @@ public class WindowStateTests extends WindowTestsBase { final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); app.mHasSurface = true; - app.mToken.mSurfaceControl = mock(SurfaceControl.class); + app.mSurfaceControl = mock(SurfaceControl.class); try { app.getFrameLw().set(10, 20, 60, 80); + app.updateSurfacePosition(t); app.seamlesslyRotateIfAllowed(t, ROTATION_0, ROTATION_90, true); assertTrue(app.mSeamlesslyRotated); + + // Verify we un-rotate the window state surface. Matrix matrix = new Matrix(); // Un-rotate 90 deg matrix.setRotate(270); // Translate it back to origin matrix.postTranslate(0, mDisplayInfo.logicalWidth); - verify(t).setMatrix(eq(app.mToken.mSurfaceControl), eq(matrix), any(float[].class)); + verify(t).setMatrix(eq(app.mSurfaceControl), eq(matrix), any(float[].class)); + + // Verify we update the position as well. + float[] currentSurfacePos = {app.mLastSurfacePosition.x, app.mLastSurfacePosition.y}; + matrix.mapPoints(currentSurfacePos); + verify(t).setPosition(eq(app.mSurfaceControl), eq(currentSurfacePos[0]), + eq(currentSurfacePos[1])); } finally { + app.mSurfaceControl = null; app.mHasSurface = false; - app.mToken.mSurfaceControl = null; } } diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java index bbc6550d5753..01b7c4f650e7 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java @@ -59,7 +59,8 @@ import java.nio.charset.StandardCharsets; */ @SmallTest @FlakyTest(bugId = 74078662) -@Presubmit +// TODO(b/116597907): Re-enable this test in postsubmit after the bug is fixed. +// @Presubmit @RunWith(AndroidJUnit4.class) public class WindowTracingTest extends WindowTestsBase { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index cbf6c6ecd331..45d2fa2d16a4 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -3383,10 +3383,44 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testIsCallerInstantApp_primaryUser() throws Exception { + ApplicationInfo info = new ApplicationInfo(); + info.privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT; + when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(info); + + assertTrue(mService.isCallerInstantApp("any", 45770, 0)); + + info.privateFlags = 0; + assertFalse(mService.isCallerInstantApp("any", 575370, 0)); + } + + @Test + public void testIsCallerInstantApp_secondaryUser() throws Exception { + ApplicationInfo info = new ApplicationInfo(); + info.privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT; + when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(10))).thenReturn(info); + when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(null); + + assertTrue(mService.isCallerInstantApp("any", 68638450, 10)); + } + + @Test + public void testResolveNotificationUid_sameApp_nonSystemUser() throws Exception { + ApplicationInfo info = new ApplicationInfo(); + info.uid = Binder.getCallingUid(); + when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(10))).thenReturn(info); + when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(null); + + int actualUid = mService.resolveNotificationUid("caller", "caller", info.uid, 10); + + assertEquals(info.uid, actualUid); + } + + @Test public void testResolveNotificationUid_sameApp() throws Exception { ApplicationInfo info = new ApplicationInfo(); info.uid = Binder.getCallingUid(); - when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(info); + when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(info); int actualUid = mService.resolveNotificationUid("caller", "caller", info.uid, 0); diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 26bd4a106ca6..08bc9bcc4003 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -141,6 +141,8 @@ public final class Call { * The caller must specify the {@link #EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE} to indicate to * Telecom which {@link PhoneAccountHandle} the {@link Call} should be handed over to. * @hide + * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated + * APIs instead. */ public static final String EVENT_REQUEST_HANDOVER = "android.telecom.event.REQUEST_HANDOVER"; @@ -149,6 +151,8 @@ public final class Call { * Extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Specifies the * {@link PhoneAccountHandle} to which a call should be handed over to. * @hide + * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated + * APIs instead. */ public static final String EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.HANDOVER_PHONE_ACCOUNT_HANDLE"; @@ -161,6 +165,8 @@ public final class Call { * {@link VideoProfile#STATE_BIDIRECTIONAL}, {@link VideoProfile#STATE_RX_ENABLED}, and * {@link VideoProfile#STATE_TX_ENABLED}. * @hide + * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated + * APIs instead. */ public static final String EXTRA_HANDOVER_VIDEO_STATE = "android.telecom.extra.HANDOVER_VIDEO_STATE"; @@ -176,6 +182,8 @@ public final class Call { * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)} * is called to initate the handover. * @hide + * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated + * APIs instead. */ public static final String EXTRA_HANDOVER_EXTRAS = "android.telecom.extra.HANDOVER_EXTRAS"; @@ -186,6 +194,8 @@ public final class Call { * <p> * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event. * @hide + * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated + * APIs instead. */ public static final String EVENT_HANDOVER_COMPLETE = "android.telecom.event.HANDOVER_COMPLETE"; @@ -198,6 +208,8 @@ public final class Call { * <p> * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event. * @hide + * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated + * APIs instead. */ public static final String EVENT_HANDOVER_SOURCE_DISCONNECTED = "android.telecom.event.HANDOVER_SOURCE_DISCONNECTED"; @@ -209,6 +221,8 @@ public final class Call { * <p> * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event. * @hide + * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated + * APIs instead. */ public static final String EVENT_HANDOVER_FAILED = "android.telecom.event.HANDOVER_FAILED"; diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index d49469233752..34603a3f056a 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -594,6 +594,8 @@ public abstract class Connection extends Conferenceable { * {@link Call#EVENT_REQUEST_HANDOVER} that the handover from this {@link Connection} has * successfully completed. * @hide + * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated + * APIs instead. */ public static final String EVENT_HANDOVER_COMPLETE = "android.telecom.event.HANDOVER_COMPLETE"; @@ -603,6 +605,8 @@ public abstract class Connection extends Conferenceable { * {@link Call#EVENT_REQUEST_HANDOVER} that the handover from this {@link Connection} has failed * to complete. * @hide + * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated + * APIs instead. */ public static final String EVENT_HANDOVER_FAILED = "android.telecom.event.HANDOVER_FAILED"; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 85901768c79f..995418ee706c 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2115,6 +2115,16 @@ public class CarrierConfigManager { public static final String KEY_CONFIG_SHOW_ORIG_DIAL_STRING_FOR_CDMA_BOOL = "config_show_orig_dial_string_for_cdma"; + /** + * Flag specifying whether to show notification(call blocking disabled) when Enhanced Call + * Blocking(KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL) is enabled and making emergency call. + * When true, notification is shown always. + * When false, notification is shown only when any setting of "Enhanced Blocked number" is + * enabled. + */ + public static final String KEY_SHOW_CALL_BLOCKING_DISABLED_NOTIFICATION_ALWAYS_BOOL = + "show_call_blocking_disabled_notification_always_bool"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -2453,6 +2463,7 @@ public class CarrierConfigManager { }); sDefaults.putString(KEY_WCDMA_DEFAULT_SIGNAL_STRENGTH_MEASUREMENT_STRING, ""); sDefaults.putBoolean(KEY_CONFIG_SHOW_ORIG_DIAL_STRING_FOR_CDMA_BOOL, false); + sDefaults.putBoolean(KEY_SHOW_CALL_BLOCKING_DISABLED_NOTIFICATION_ALWAYS_BOOL, false); } /** diff --git a/telephony/java/android/telephony/MbmsGroupCallSession.java b/telephony/java/android/telephony/MbmsGroupCallSession.java new file mode 100644 index 000000000000..e3737976adf7 --- /dev/null +++ b/telephony/java/android/telephony/MbmsGroupCallSession.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.content.ComponentName; +import android.content.Context; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.telephony.mbms.GroupCall; +import android.telephony.mbms.GroupCallCallback; +import android.telephony.mbms.InternalGroupCallCallback; +import android.telephony.mbms.InternalGroupCallSessionCallback; +import android.telephony.mbms.MbmsErrors; +import android.telephony.mbms.MbmsGroupCallSessionCallback; +import android.telephony.mbms.MbmsUtils; +import android.telephony.mbms.vendor.IMbmsGroupCallService; +import android.util.ArraySet; +import android.util.Log; + +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +/** + * This class provides functionality for accessing group call functionality over MBMS. + */ +public class MbmsGroupCallSession implements AutoCloseable { + private static final String LOG_TAG = "MbmsGroupCallSession"; + + /** + * Service action which must be handled by the middleware implementing the MBMS group call + * interface. + * @hide + */ + @SystemApi + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String MBMS_GROUP_CALL_SERVICE_ACTION = + "android.telephony.action.EmbmsGroupCall"; + + /** + * Metadata key that specifies the component name of the service to bind to for group calls. + * @hide + */ + @TestApi + public static final String MBMS_GROUP_CALL_SERVICE_OVERRIDE_METADATA = + "mbms-group-call-service-override"; + + private static AtomicBoolean sIsInitialized = new AtomicBoolean(false); + + private AtomicReference<IMbmsGroupCallService> mService = new AtomicReference<>(null); + private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + sIsInitialized.set(false); + mInternalCallback.onError(MbmsErrors.ERROR_MIDDLEWARE_LOST, + "Received death notification"); + } + }; + + private InternalGroupCallSessionCallback mInternalCallback; + private Set<GroupCall> mKnownActiveGroupCalls = new ArraySet<>(); + + private final Context mContext; + private int mSubscriptionId; + + /** @hide */ + private MbmsGroupCallSession(Context context, Executor executor, int subscriptionId, + MbmsGroupCallSessionCallback callback) { + mContext = context; + mSubscriptionId = subscriptionId; + mInternalCallback = new InternalGroupCallSessionCallback(callback, executor); + } + + /** + * Create a new {@link MbmsGroupCallSession} using the given subscription ID. + * + * You may only have one instance of {@link MbmsGroupCallSession} per UID. If you call this + * method while there is an active instance of {@link MbmsGroupCallSession} in your process + * (in other words, one that has not had {@link #close()} called on it), this method will + * throw an {@link IllegalStateException}. If you call this method in a different process + * running under the same UID, an error will be indicated via + * {@link MbmsGroupCallSessionCallback#onError(int, String)}. + * + * Note that initialization may fail asynchronously. If you wish to try again after you + * receive such an asynchronous error, you must call {@link #close()} on the instance of + * {@link MbmsGroupCallSession} that you received before calling this method again. + * + * @param context The {@link Context} to use. + * @param executor The executor on which you wish to execute callbacks. + * @param subscriptionId The subscription ID to use. + * @param callback A callback object on which you wish to receive results of asynchronous + * operations. + * @return An instance of {@link MbmsGroupCallSession}, or null if an error occurred. + */ + public static @Nullable MbmsGroupCallSession create(@NonNull Context context, + @NonNull Executor executor, int subscriptionId, + final @NonNull MbmsGroupCallSessionCallback callback) { + if (!sIsInitialized.compareAndSet(false, true)) { + throw new IllegalStateException("Cannot create two instances of MbmsGroupCallSession"); + } + MbmsGroupCallSession session = new MbmsGroupCallSession(context, executor, + subscriptionId, callback); + + final int result = session.bindAndInitialize(); + if (result != MbmsErrors.SUCCESS) { + sIsInitialized.set(false); + executor.execute(new Runnable() { + @Override + public void run() { + callback.onError(result, null); + } + }); + return null; + } + return session; + } + + /** + * Create a new {@link MbmsGroupCallSession} using the system default data subscription ID. + * See {@link #create(Context, Executor, int, MbmsGroupCallSessionCallback)}. + */ + public static MbmsGroupCallSession create(@NonNull Context context, + @NonNull Executor executor, @NonNull MbmsGroupCallSessionCallback callback) { + return create(context, executor, SubscriptionManager.getDefaultSubscriptionId(), callback); + } + + /** + * Terminates this instance. Also terminates + * any group calls spawned from this instance as if + * {@link GroupCall#close()} had been called on them. After this method returns, + * no further callbacks originating from the middleware will be enqueued on the provided + * instance of {@link MbmsGroupCallSessionCallback}, but callbacks that have already been + * enqueued will still be delivered. + * + * It is safe to call {@link #create(Context, Executor, int, MbmsGroupCallSessionCallback)} to + * obtain another instance of {@link MbmsGroupCallSession} immediately after this method + * returns. + * + * May throw an {@link IllegalStateException} + */ + public void close() { + try { + IMbmsGroupCallService groupCallService = mService.get(); + if (groupCallService == null) { + // Ignore and return, assume already disposed. + return; + } + groupCallService.dispose(mSubscriptionId); + for (GroupCall s : mKnownActiveGroupCalls) { + s.getCallback().stop(); + } + mKnownActiveGroupCalls.clear(); + } catch (RemoteException e) { + // Ignore for now + } finally { + mService.set(null); + sIsInitialized.set(false); + mInternalCallback.stop(); + } + } + + /** + * Starts the requested group call, reporting status to the indicated callback. + * Returns an object used to control that call. + * + * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} + * + * Asynchronous errors through the callback include any of the errors in + * {@link MbmsErrors.GeneralErrors}. + * + * @param executor The executor on which you wish to execute callbacks for this stream. + * @param tmgi The TMGI, an identifier for the group call you want to join. + * @param saiArray An array of SAIs for the group call that should be negotiated separately with + * the carrier. + * @param frequencyArray An array of frequencies for the group call that should be negotiated + * separately with the carrier. + * @param callback The callback that you want to receive information about the call on. + * @return An instance of {@link GroupCall} through which the call can be controlled. + * May be {@code null} if an error occurred. + */ + public @Nullable GroupCall startGroupCall(@NonNull Executor executor, long tmgi, int[] saiArray, + int[] frequencyArray, @NonNull GroupCallCallback callback) { + IMbmsGroupCallService groupCallService = mService.get(); + if (groupCallService == null) { + throw new IllegalStateException("Middleware not yet bound"); + } + + InternalGroupCallCallback serviceCallback = new InternalGroupCallCallback( + callback, executor); + + GroupCall serviceForApp = new GroupCall(mSubscriptionId, + groupCallService, this, tmgi, serviceCallback); + mKnownActiveGroupCalls.add(serviceForApp); + + try { + int returnCode = groupCallService.startGroupCall( + mSubscriptionId, tmgi, saiArray, frequencyArray, serviceCallback); + if (returnCode == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not return an unknown error code"); + } + if (returnCode != MbmsErrors.SUCCESS) { + mInternalCallback.onError(returnCode, null); + return null; + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Remote process died"); + mService.set(null); + sIsInitialized.set(false); + mInternalCallback.onError(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); + return null; + } + + return serviceForApp; + } + + /** @hide */ + public void onGroupCallStopped(GroupCall service) { + mKnownActiveGroupCalls.remove(service); + } + + private int bindAndInitialize() { + return MbmsUtils.startBinding(mContext, MBMS_GROUP_CALL_SERVICE_ACTION, + new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + IMbmsGroupCallService groupCallService = + IMbmsGroupCallService.Stub.asInterface(service); + int result; + try { + result = groupCallService.initialize(mInternalCallback, + mSubscriptionId); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Service died before initialization"); + mInternalCallback.onError( + MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE, + e.toString()); + sIsInitialized.set(false); + return; + } catch (RuntimeException e) { + Log.e(LOG_TAG, "Runtime exception during initialization"); + mInternalCallback.onError( + MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE, + e.toString()); + sIsInitialized.set(false); + return; + } + if (result == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not return" + + " an unknown error code"); + } + if (result != MbmsErrors.SUCCESS) { + mInternalCallback.onError(result, + "Error returned during initialization"); + sIsInitialized.set(false); + return; + } + try { + groupCallService.asBinder().linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + mInternalCallback.onError(MbmsErrors.ERROR_MIDDLEWARE_LOST, + "Middleware lost during initialization"); + sIsInitialized.set(false); + return; + } + mService.set(groupCallService); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + sIsInitialized.set(false); + mService.set(null); + } + }); + } +} diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index a8bcbe33966a..d58b932f2851 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -2091,10 +2091,37 @@ public class TelephonyManager { /** Max network type number. Update as new types are added. Don't add negative types. {@hide} */ public static final int MAX_NETWORK_TYPE = NETWORK_TYPE_LTE_CA; + + /** @hide */ + @IntDef({ + NETWORK_TYPE_UNKNOWN, + NETWORK_TYPE_GPRS, + NETWORK_TYPE_EDGE, + NETWORK_TYPE_UMTS, + NETWORK_TYPE_CDMA, + NETWORK_TYPE_EVDO_0, + NETWORK_TYPE_EVDO_A, + NETWORK_TYPE_1xRTT, + NETWORK_TYPE_HSDPA, + NETWORK_TYPE_HSUPA, + NETWORK_TYPE_HSPA, + NETWORK_TYPE_IDEN, + NETWORK_TYPE_EVDO_B, + NETWORK_TYPE_LTE, + NETWORK_TYPE_EHRPD, + NETWORK_TYPE_HSPAP, + NETWORK_TYPE_GSM, + NETWORK_TYPE_TD_SCDMA, + NETWORK_TYPE_IWLAN, + NETWORK_TYPE_LTE_CA, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NetworkType{} + /** * @return the NETWORK_TYPE_xxxx for current data connection. */ - public int getNetworkType() { + public @NetworkType int getNetworkType() { try { ITelephony telephony = getITelephony(); if (telephony != null) { @@ -2139,24 +2166,24 @@ public class TelephonyManager { * @hide */ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - @UnsupportedAppUsage - public int getNetworkType(int subId) { - try { - ITelephony telephony = getITelephony(); - if (telephony != null) { - return telephony.getNetworkTypeForSubscriber(subId, getOpPackageName()); - } else { - // This can happen when the ITelephony interface is not up yet. - return NETWORK_TYPE_UNKNOWN; - } - } catch(RemoteException ex) { - // This shouldn't happen in the normal case - return NETWORK_TYPE_UNKNOWN; - } catch (NullPointerException ex) { - // This could happen before phone restarts due to crashing - return NETWORK_TYPE_UNKNOWN; - } - } + @UnsupportedAppUsage + public int getNetworkType(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getNetworkTypeForSubscriber(subId, getOpPackageName()); + } else { + // This can happen when the ITelephony interface is not up yet. + return NETWORK_TYPE_UNKNOWN; + } + } catch (RemoteException ex) { + // This shouldn't happen in the normal case + return NETWORK_TYPE_UNKNOWN; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return NETWORK_TYPE_UNKNOWN; + } + } /** * Returns a constant indicating the radio technology (network type) @@ -2189,7 +2216,7 @@ public class TelephonyManager { */ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public int getDataNetworkType() { + public @NetworkType int getDataNetworkType() { return getDataNetworkType(getSubId(SubscriptionManager.getDefaultDataSubscriptionId())); } @@ -2229,7 +2256,7 @@ public class TelephonyManager { */ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public int getVoiceNetworkType() { + public @NetworkType int getVoiceNetworkType() { return getVoiceNetworkType(getSubId()); } @@ -4108,11 +4135,16 @@ public class TelephonyManager { } /** - * Returns the IMS home network domain name that was loaded from the ISIM. - * @return the IMS domain name, or null if not present or not loaded + * Returns the IMS home network domain name that was loaded from the ISIM {@see #APPTYPE_ISIM}. + * @return the IMS domain name. Returns {@code null} if ISIM hasn't been loaded or IMS domain + * hasn't been loaded or isn't present on the ISIM. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} * @hide */ - @UnsupportedAppUsage + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain() { try { IPhoneSubInfo info = getSubscriberInfo(); diff --git a/telephony/java/android/telephony/mbms/GroupCall.java b/telephony/java/android/telephony/mbms/GroupCall.java new file mode 100644 index 000000000000..9aca18e07812 --- /dev/null +++ b/telephony/java/android/telephony/mbms/GroupCall.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.mbms; + +import android.annotation.IntDef; +import android.os.RemoteException; +import android.telephony.MbmsGroupCallSession; +import android.telephony.mbms.vendor.IMbmsGroupCallService; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Class used to represent a single MBMS group call. After a call has been started with + * {@link MbmsGroupCallSession#startGroupCall}, + * this class is used to hold information about the call and control it. + */ +public class GroupCall implements AutoCloseable { + private static final String LOG_TAG = "MbmsGroupCall"; + + /** + * The state of a group call, reported via + * {@link GroupCallCallback#onGroupCallStateChanged(int, int)} + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "STATE_" }, value = {STATE_STOPPED, STATE_STARTED, STATE_STALLED}) + public @interface GroupCallState {} + public static final int STATE_STOPPED = 1; + public static final int STATE_STARTED = 2; + public static final int STATE_STALLED = 3; + + /** + * The reason for a call state change, reported via + * {@link GroupCallCallback#onGroupCallStateChanged(int, int)} + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "REASON_" }, + value = {REASON_BY_USER_REQUEST, REASON_FREQUENCY_CONFLICT, + REASON_OUT_OF_MEMORY, REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE, + REASON_LEFT_MBMS_BROADCAST_AREA, REASON_NONE}) + public @interface GroupCallStateChangeReason {} + + /** + * Indicates that the middleware does not have a reason to provide for the state change. + */ + public static final int REASON_NONE = 0; + + /** + * State changed due to a call to {@link #close()} or + * {@link MbmsGroupCallSession#startGroupCall} + */ + public static final int REASON_BY_USER_REQUEST = 1; + + // 2 is unused to match up with streaming. + + /** + * State changed due to a frequency conflict with another requested call. + */ + public static final int REASON_FREQUENCY_CONFLICT = 3; + + /** + * State changed due to the middleware running out of memory + */ + public static final int REASON_OUT_OF_MEMORY = 4; + + /** + * State changed due to the device leaving the home carrier's LTE network. + */ + public static final int REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE = 5; + + /** + * State changed due to the device leaving the area where this call is being broadcast. + */ + public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 6; + + private final int mSubscriptionId; + private final long mTmgi; + private final MbmsGroupCallSession mParentSession; + private final InternalGroupCallCallback mCallback; + private IMbmsGroupCallService mService; + + /** + * @hide + */ + public GroupCall(int subscriptionId, + IMbmsGroupCallService service, + MbmsGroupCallSession session, + long tmgi, + InternalGroupCallCallback callback) { + mSubscriptionId = subscriptionId; + mParentSession = session; + mService = service; + mTmgi = tmgi; + mCallback = callback; + } + + /** + * Retrieve the TMGI (Temporary Mobile Group Identity) corresponding to this call. + */ + public long getTmgi() { + return mTmgi; + } + + /** + * Send an update to the middleware when the SAI (Service Area Identifier) list and frequency + * information of the group call has * changed. Callers must obtain this information from the + * wireless carrier independently. + * @param saiArray New array of SAIs that the call is available on. + * @param frequencyArray New array of frequencies that the call is available on. + */ + public void updateGroupCall(int[] saiArray, int[] frequencyArray) { + if (mService == null) { + throw new IllegalStateException("No group call service attached"); + } + + try { + mService.updateGroupCall(mSubscriptionId, mTmgi, saiArray, frequencyArray); + } catch (RemoteException e) { + Log.w(LOG_TAG, "Remote process died"); + mService = null; + sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); + } finally { + mParentSession.onGroupCallStopped(this); + } + } + + /** + * Stop this group call. Further operations on this object will fail with an + * {@link IllegalStateException}. + * + * May throw an {@link IllegalStateException} + */ + @Override + public void close() { + if (mService == null) { + throw new IllegalStateException("No group call service attached"); + } + + try { + mService.stopGroupCall(mSubscriptionId, mTmgi); + } catch (RemoteException e) { + Log.w(LOG_TAG, "Remote process died"); + mService = null; + sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); + } finally { + mParentSession.onGroupCallStopped(this); + } + } + + /** @hide */ + public InternalGroupCallCallback getCallback() { + return mCallback; + } + + private void sendErrorToApp(int errorCode, String message) { + mCallback.onError(errorCode, message); + } +} + diff --git a/telephony/java/android/telephony/mbms/GroupCallCallback.java b/telephony/java/android/telephony/mbms/GroupCallCallback.java new file mode 100644 index 000000000000..001bb02aad94 --- /dev/null +++ b/telephony/java/android/telephony/mbms/GroupCallCallback.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.mbms; + +import android.annotation.IntDef; +import android.annotation.Nullable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A callback class for use when the application is in a group call. The middleware + * will provide updates on the status of the call via this callback. + */ +public class GroupCallCallback { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + MbmsErrors.ERROR_NO_UNIQUE_MIDDLEWARE, + MbmsErrors.ERROR_MIDDLEWARE_LOST, + MbmsErrors.ERROR_MIDDLEWARE_NOT_BOUND, + MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_NOT_YET_READY, + MbmsErrors.GeneralErrors.ERROR_OUT_OF_MEMORY, + MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE, + MbmsErrors.GeneralErrors.ERROR_IN_E911, + MbmsErrors.GeneralErrors.ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE, + MbmsErrors.GeneralErrors.ERROR_UNABLE_TO_READ_SIM, + MbmsErrors.GeneralErrors.ERROR_CARRIER_CHANGE_NOT_ALLOWED}, prefix = { "ERROR_" }) + private @interface GroupCallError{} + + /** + * Indicates broadcast signal strength is not available for this call. + * + * This may be due to the call no longer being available due to geography + * or timing (end of service) + */ + public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1; + + /** + * Called by the middleware when it has detected an error condition in this group call. The + * possible error codes are listed in {@link MbmsErrors}. + * @param errorCode The error code. + * @param message A human-readable message generated by the middleware for debugging purposes. + */ + public void onError(@GroupCallError int errorCode, @Nullable String message) { + // default implementation empty + } + + /** + * Called to indicate this call has changed state. + * + * See {@link GroupCall#STATE_STOPPED}, {@link GroupCall#STATE_STARTED} + * and {@link GroupCall#STATE_STALLED}. + */ + public void onGroupCallStateChanged(@GroupCall.GroupCallState int state, + @GroupCall.GroupCallStateChangeReason int reason) { + // default implementation empty + } + + /** + * Broadcast Signal Strength updated. + * + * This signal strength is the BROADCAST signal strength which, + * depending on technology in play and it's deployment, may be + * stronger or weaker than the traditional UNICAST signal + * strength. It a simple int from 0-4 for valid levels or + * {@link #SIGNAL_STRENGTH_UNAVAILABLE} if broadcast is not available + * for this call due to timing, geography or popularity. + */ + public void onBroadcastSignalStrengthUpdated(int signalStrength) { + // default implementation empty + } +} diff --git a/telephony/java/android/telephony/mbms/IGroupCallCallback.aidl b/telephony/java/android/telephony/mbms/IGroupCallCallback.aidl new file mode 100755 index 000000000000..844b6344a34c --- /dev/null +++ b/telephony/java/android/telephony/mbms/IGroupCallCallback.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.mbms; + +/** + * @hide + */ +oneway interface IGroupCallCallback { + void onError(int errorCode, String message); + void onGroupCallStateChanged(int state, int reason); + void onBroadcastSignalStrengthUpdated(int signalStrength); +} diff --git a/telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl new file mode 100755 index 000000000000..1a1c7f8af5df --- /dev/null +++ b/telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl @@ -0,0 +1,33 @@ +/* +** Copyright 2018, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.telephony.mbms; + +import java.util.List; + +/** + * @hide + */ +oneway interface IMbmsGroupCallSessionCallback +{ + void onError(int errorCode, String message); + + void onAvailableSaisUpdated(in List currentSai, in List availableSais); + + void onServiceInterfaceAvailable(String interfaceName, int index); + + void onMiddlewareReady(); +} diff --git a/telephony/java/android/telephony/mbms/InternalGroupCallCallback.java b/telephony/java/android/telephony/mbms/InternalGroupCallCallback.java new file mode 100644 index 000000000000..2910bb313d84 --- /dev/null +++ b/telephony/java/android/telephony/mbms/InternalGroupCallCallback.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.mbms; + +import android.os.Binder; + +import java.util.concurrent.Executor; + +/** @hide */ +public class InternalGroupCallCallback extends IGroupCallCallback.Stub { + private final GroupCallCallback mAppCallback; + private final Executor mExecutor; + private volatile boolean mIsStopped = false; + + public InternalGroupCallCallback(GroupCallCallback appCallback, + Executor executor) { + mAppCallback = appCallback; + mExecutor = executor; + } + + @Override + public void onError(final int errorCode, final String message) { + if (mIsStopped) { + return; + } + + mExecutor.execute(new Runnable() { + @Override + public void run() { + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onError(errorCode, message); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }); + } + + @Override + public void onGroupCallStateChanged(final int state, final int reason) { + if (mIsStopped) { + return; + } + + mExecutor.execute(new Runnable() { + @Override + public void run() { + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onGroupCallStateChanged(state, reason); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }); + } + + @Override + public void onBroadcastSignalStrengthUpdated(final int signalStrength) { + if (mIsStopped) { + return; + } + + mExecutor.execute(new Runnable() { + @Override + public void run() { + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onBroadcastSignalStrengthUpdated(signalStrength); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }); + } + + /** Prevents this callback from calling the app */ + public void stop() { + mIsStopped = true; + } +} diff --git a/telephony/java/android/telephony/mbms/InternalGroupCallSessionCallback.java b/telephony/java/android/telephony/mbms/InternalGroupCallSessionCallback.java new file mode 100644 index 000000000000..4c9cf4dd7c92 --- /dev/null +++ b/telephony/java/android/telephony/mbms/InternalGroupCallSessionCallback.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.mbms; + +import android.os.Binder; + +import java.util.List; +import java.util.concurrent.Executor; + +/** @hide */ +public class InternalGroupCallSessionCallback extends IMbmsGroupCallSessionCallback.Stub { + private final Executor mExecutor; + private final MbmsGroupCallSessionCallback mAppCallback; + private volatile boolean mIsStopped = false; + + public InternalGroupCallSessionCallback(MbmsGroupCallSessionCallback appCallback, + Executor executor) { + mAppCallback = appCallback; + mExecutor = executor; + } + + @Override + public void onError(final int errorCode, final String message) { + if (mIsStopped) { + return; + } + + mExecutor.execute(new Runnable() { + @Override + public void run() { + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onError(errorCode, message); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }); + } + + @Override + public void onAvailableSaisUpdated(final List currentSais, final List availableSais) { + if (mIsStopped) { + return; + } + + mExecutor.execute(new Runnable() { + @Override + public void run() { + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onAvailableSaisUpdated(currentSais, availableSais); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }); + } + + @Override + public void onServiceInterfaceAvailable(final String interfaceName, final int index) { + if (mIsStopped) { + return; + } + + mExecutor.execute(new Runnable() { + @Override + public void run() { + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onServiceInterfaceAvailable(interfaceName, index); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }); + } + + @Override + public void onMiddlewareReady() { + if (mIsStopped) { + return; + } + + mExecutor.execute(new Runnable() { + @Override + public void run() { + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onMiddlewareReady(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }); + } + + /** Prevents this callback from calling the app */ + public void stop() { + mIsStopped = true; + } +} diff --git a/telephony/java/android/telephony/mbms/MbmsGroupCallSessionCallback.java b/telephony/java/android/telephony/mbms/MbmsGroupCallSessionCallback.java new file mode 100644 index 000000000000..7da734ee5837 --- /dev/null +++ b/telephony/java/android/telephony/mbms/MbmsGroupCallSessionCallback.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.mbms; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.content.Context; +import android.telephony.MbmsGroupCallSession; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * A callback class that is used to receive information from the middleware on MBMS group-call + * services. An instance of this object should be passed into + * {@link MbmsGroupCallSession#create(Context, Executor, int, MbmsGroupCallSessionCallback)}. + */ +public class MbmsGroupCallSessionCallback { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + MbmsErrors.ERROR_NO_UNIQUE_MIDDLEWARE, + MbmsErrors.ERROR_MIDDLEWARE_LOST, + MbmsErrors.ERROR_MIDDLEWARE_NOT_BOUND, + MbmsErrors.InitializationErrors.ERROR_APP_PERMISSIONS_NOT_GRANTED, + MbmsErrors.InitializationErrors.ERROR_DUPLICATE_INITIALIZE, + MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE, + MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_NOT_YET_READY, + MbmsErrors.GeneralErrors.ERROR_OUT_OF_MEMORY, + MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE, + MbmsErrors.GeneralErrors.ERROR_IN_E911, + MbmsErrors.GeneralErrors.ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE, + MbmsErrors.GeneralErrors.ERROR_UNABLE_TO_READ_SIM, + MbmsErrors.GeneralErrors.ERROR_CARRIER_CHANGE_NOT_ALLOWED}, prefix = { "ERROR_" }) + private @interface GroupCallError{} + + /** + * Called by the middleware when it has detected an error condition. The possible error codes + * are listed in {@link MbmsErrors}. + * @param errorCode The error code. + * @param message A human-readable message generated by the middleware for debugging purposes. + */ + public void onError(@GroupCallError int errorCode, @Nullable String message) { + } + + /** + * Indicates that the list of currently available SAIs has been updated. The app may use this + * information to filter the list of group calls when displaying available group calls to + * the user by matching the SAIs with a list of group calls separately negotiated with the + * carrier. The app may also report the aggregate list of SAIs to the group call application + * server so that a network entity can determine when, and where to activate the broadcast of + * particular group calls. + * @param currentSais The available SAIs on the current cell. + * @param availableSais A list of lists of available SAIS in neighboring cells, where each list + * contains the available SAIs in an individual cell. + */ + public void onAvailableSaisUpdated(List<Integer> currentSais, + List<List<Integer>> availableSais) { + } + + /** + * Called soon after the app calls {@link MbmsGroupCallSession#create}. The information supplied + * via this callback may be used to establish a data-link interface with the modem before the + * middleware is ready. + * Note that this method may be called before {@link #onMiddlewareReady()}. + * + * @param interfaceName The interface name for the data link. + * @param index The index for the data link. + */ + public void onServiceInterfaceAvailable(String interfaceName, int index) { + } + + /** + * Called to indicate that the middleware has been initialized and is ready. + * + * Before this method is called, calling any method on an instance of + * {@link MbmsGroupCallSession} will result in an {@link IllegalStateException} or an error + * delivered via {@link #onError(int, String)} with error code + * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}. + */ + public void onMiddlewareReady() { + } +} diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java index 06b2120b59c1..95b4d37e5840 100644 --- a/telephony/java/android/telephony/mbms/MbmsUtils.java +++ b/telephony/java/android/telephony/mbms/MbmsUtils.java @@ -23,6 +23,7 @@ import android.content.ServiceConnection; import android.content.pm.*; import android.content.pm.ServiceInfo; import android.telephony.MbmsDownloadSession; +import android.telephony.MbmsGroupCallSession; import android.telephony.MbmsStreamingSession; import android.util.Log; @@ -59,6 +60,9 @@ public class MbmsUtils { case MbmsStreamingSession.MBMS_STREAMING_SERVICE_ACTION: metaDataKey = MbmsStreamingSession.MBMS_STREAMING_SERVICE_OVERRIDE_METADATA; break; + case MbmsGroupCallSession.MBMS_GROUP_CALL_SERVICE_ACTION: + metaDataKey = MbmsGroupCallSession.MBMS_GROUP_CALL_SERVICE_OVERRIDE_METADATA; + break; } if (metaDataKey == null) { return null; diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl new file mode 100755 index 000000000000..721256a95396 --- /dev/null +++ b/telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl @@ -0,0 +1,39 @@ +/* +** Copyright 2017, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.telephony.mbms.vendor; + +import android.net.Uri; +import android.telephony.mbms.IMbmsGroupCallSessionCallback; +import android.telephony.mbms.IGroupCallCallback; + +/** + * @hide + */ +interface IMbmsGroupCallService +{ + int initialize(IMbmsGroupCallSessionCallback callback, int subId); + + void stopGroupCall(int subId, long tmgi); + + void updateGroupCall(int subscriptionId, long tmgi, in int[] saiArray, + in int[] frequencyArray); + + int startGroupCall(int subscriptionId, long tmgi, in int[] saiArray, + in int[] frequencyArray, IGroupCallCallback callback); + + void dispose(int subId); +} diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsGroupCallServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsGroupCallServiceBase.java new file mode 100644 index 000000000000..3734ca7d6fc9 --- /dev/null +++ b/telephony/java/android/telephony/mbms/vendor/MbmsGroupCallServiceBase.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.mbms.vendor; + +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.telephony.mbms.GroupCallCallback; +import android.telephony.mbms.IGroupCallCallback; +import android.telephony.mbms.IMbmsGroupCallSessionCallback; +import android.telephony.mbms.MbmsErrors; +import android.telephony.mbms.MbmsGroupCallSessionCallback; +import android.telephony.mbms.vendor.IMbmsGroupCallService.Stub; + +import java.util.List; + +/** + * Base class for MBMS group-call services. The middleware should override this class to implement + * its {@link Service} for group calls + * Subclasses should call this class's {@link #onBind} method to obtain an {@link IBinder} if they + * override {@link #onBind}. + * @hide + */ +@SystemApi +@TestApi +public class MbmsGroupCallServiceBase extends Service { + private final IBinder mInterface = new Stub() { + @Override + public int initialize(final IMbmsGroupCallSessionCallback callback, + final int subscriptionId) throws RemoteException { + if (callback == null) { + throw new NullPointerException("Callback must not be null"); + } + + final int uid = Binder.getCallingUid(); + + int result = MbmsGroupCallServiceBase.this.initialize( + new MbmsGroupCallSessionCallback() { + @Override + public void onError(final int errorCode, final String message) { + try { + if (errorCode == MbmsErrors.UNKNOWN) { + throw new IllegalArgumentException( + "Middleware cannot send an unknown error."); + } + callback.onError(errorCode, message); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } + } + + @Override + public void onAvailableSaisUpdated(final List currentSais, + final List availableSais) { + try { + callback.onAvailableSaisUpdated(currentSais, availableSais); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } + } + + @Override + public void onServiceInterfaceAvailable(final String interfaceName, + final int index) { + try { + callback.onServiceInterfaceAvailable(interfaceName, index); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } + } + + @Override + public void onMiddlewareReady() { + try { + callback.onMiddlewareReady(); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } + } + }, subscriptionId); + + if (result == MbmsErrors.SUCCESS) { + callback.asBinder().linkToDeath(new DeathRecipient() { + @Override + public void binderDied() { + onAppCallbackDied(uid, subscriptionId); + } + }, 0); + } + + return result; + } + + @Override + public void stopGroupCall(int subId, long tmgi) { + MbmsGroupCallServiceBase.this.stopGroupCall(subId, tmgi); + } + + @Override + public void updateGroupCall(int subscriptionId, long tmgi, int[] saiArray, + int[] frequencyArray) { + MbmsGroupCallServiceBase.this.updateGroupCall( + subscriptionId, tmgi, saiArray, frequencyArray); + } + + @Override + public int startGroupCall(final int subscriptionId, final long tmgi, final int[] saiArray, + final int[] frequencyArray, final IGroupCallCallback callback) + throws RemoteException { + if (callback == null) { + throw new NullPointerException("Callback must not be null"); + } + + final int uid = Binder.getCallingUid(); + + int result = MbmsGroupCallServiceBase.this.startGroupCall( + subscriptionId, tmgi, saiArray, frequencyArray, new GroupCallCallback() { + @Override + public void onError(final int errorCode, final String message) { + try { + if (errorCode == MbmsErrors.UNKNOWN) { + throw new IllegalArgumentException( + "Middleware cannot send an unknown error."); + } + callback.onError(errorCode, message); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } + } + + public void onGroupCallStateChanged(int state, int reason) { + try { + callback.onGroupCallStateChanged(state, reason); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } + } + + public void onBroadcastSignalStrengthUpdated(int signalStrength) { + try { + callback.onBroadcastSignalStrengthUpdated(signalStrength); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } + } + }); + + if (result == MbmsErrors.SUCCESS) { + callback.asBinder().linkToDeath(new DeathRecipient() { + @Override + public void binderDied() { + onAppCallbackDied(uid, subscriptionId); + } + }, 0); + } + + return result; + } + + @Override + public void dispose(int subId) throws RemoteException { + MbmsGroupCallServiceBase.this.dispose(subId); + } + }; + + /** + * Initialize the group call service for this app and subscription ID, registering the callback. + * + * May throw an {@link IllegalArgumentException} or a {@link SecurityException}, which + * will be intercepted and passed to the app as + * {@link MbmsErrors.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE} + * + * May return any value from {@link MbmsErrors.InitializationErrors} + * or {@link MbmsErrors#SUCCESS}. Non-successful error codes will be passed to the app via + * {@link IMbmsGroupCallSessionCallback#onError(int, String)}. + * + * @param callback The callback to use to communicate with the app. + * @param subscriptionId The subscription ID to use. + */ + public int initialize(MbmsGroupCallSessionCallback callback, int subscriptionId) + throws RemoteException { + throw new UnsupportedOperationException("Not implemented"); + } + + /** + * Starts a particular group call. This method may perform asynchronous work. When + * the call is ready for consumption, the middleware should inform the app via + * {@link IGroupCallCallback#onGroupCallStateChanged(int, int)}. + * + * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} + * + * @param subscriptionId The subscription id to use. + * @param tmgi The TMGI, an identifier for the group call. + * @param saiArray An array of SAIs for the group call. + * @param frequencyArray An array of frequencies for the group call. + * @param callback The callback object on which the app wishes to receive updates. + * @return Any error in {@link MbmsErrors.GeneralErrors} + */ + public int startGroupCall(int subscriptionId, long tmgi, int[] saiArray, int[] frequencyArray, + GroupCallCallback callback) { + throw new UnsupportedOperationException("Not implemented"); + } + + /** + * Stop the group call identified by {@code tmgi}. + * + * The callback provided via {@link #startGroupCall} should no longer be + * used after this method has called by the app. + * + * May throw an {@link IllegalStateException} + * + * @param subscriptionId The subscription id to use. + * @param tmgi The TMGI for the call to stop. + */ + public void stopGroupCall(int subscriptionId, long tmgi) { + throw new UnsupportedOperationException("Not implemented"); + } + + /** + * Called when the app receives new SAI and frequency information for the group call identified + * by {@code tmgi}. + * @param saiArray New array of SAIs that the call is available on. + * @param frequencyArray New array of frequencies that the call is available on. + */ + public void updateGroupCall(int subscriptionId, long tmgi, int[] saiArray, + int[] frequencyArray) { + throw new UnsupportedOperationException("Not implemented"); + } + + /** + * Signals that the app wishes to dispose of the session identified by the + * {@code subscriptionId} argument and the caller's uid. No notification back to the + * app is required for this operation, and the corresponding callback provided via + * {@link #initialize} should no longer be used + * after this method has been called by the app. + * + * May throw an {@link IllegalStateException} + * + * @param subscriptionId The subscription id to use. + */ + public void dispose(int subscriptionId) throws RemoteException { + throw new UnsupportedOperationException("Not implemented"); + } + + /** + * Indicates that the app identified by the given UID and subscription ID has died. + * @param uid the UID of the app, as returned by {@link Binder#getCallingUid()}. + * @param subscriptionId The subscription ID the app is using. + */ + public void onAppCallbackDied(int uid, int subscriptionId) { + } + + @Override + public IBinder onBind(Intent intent) { + return mInterface; + } +} |