diff options
389 files changed, 11092 insertions, 4724 deletions
diff --git a/StubLibraries.bp b/StubLibraries.bp index bff222e7d42f..c61f7c684efd 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -437,10 +437,6 @@ droidstubs { name: "hwbinder-stubs-docs", srcs: [ "core/java/android/os/HidlSupport.java", - "core/java/android/annotation/IntDef.java", - "core/java/android/annotation/IntRange.java", - "core/java/android/annotation/NonNull.java", - "core/java/android/annotation/SystemApi.java", "core/java/android/os/HidlMemory.java", "core/java/android/os/HwBinder.java", "core/java/android/os/HwBlob.java", @@ -466,6 +462,7 @@ droidstubs { java_library_static { name: "hwbinder.stubs", sdk_version: "core_current", + libs: ["stub-annotations"], srcs: [ ":hwbinder-stubs-docs", ], diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index 3d129d8548eb..20ce13322fd2 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -135,6 +135,10 @@ java_sdk_library { ":updatable-media-srcs", ], + api_lint: { + enabled: false, + }, + libs: [ "framework_media_annotation", ], diff --git a/apex/media/framework/api/system-current.txt b/apex/media/framework/api/system-current.txt index 6158e2ece55f..1d912ebc71fa 100644 --- a/apex/media/framework/api/system-current.txt +++ b/apex/media/framework/api/system-current.txt @@ -3,38 +3,19 @@ package android.media { public final class MediaTranscodeManager { method @Nullable public android.media.MediaTranscodeManager.TranscodingSession enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener); - field public static final int PRIORITY_REALTIME = 1; // 0x1 - field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1 } @java.lang.FunctionalInterface public static interface MediaTranscodeManager.OnTranscodingFinishedListener { method public void onTranscodingFinished(@NonNull android.media.MediaTranscodeManager.TranscodingSession); } - public static final class MediaTranscodeManager.TranscodingRequest { + public abstract static class MediaTranscodeManager.TranscodingRequest { method public int getClientPid(); method public int getClientUid(); method @Nullable public android.os.ParcelFileDescriptor getDestinationFileDescriptor(); method @NonNull public android.net.Uri getDestinationUri(); - method public int getPriority(); method @Nullable public android.os.ParcelFileDescriptor getSourceFileDescriptor(); method @NonNull public android.net.Uri getSourceUri(); - method public int getType(); - method @Nullable public android.media.MediaFormat getVideoTrackFormat(); - } - - public static final class MediaTranscodeManager.TranscodingRequest.Builder { - ctor public MediaTranscodeManager.TranscodingRequest.Builder(); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest build(); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientPid(int); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientUid(int); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationFileDescriptor(@NonNull android.os.ParcelFileDescriptor); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationUri(@NonNull android.net.Uri); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setPriority(int); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceFileDescriptor(@NonNull android.os.ParcelFileDescriptor); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceUri(@NonNull android.net.Uri); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setType(int); - method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setVideoTrackFormat(@NonNull android.media.MediaFormat); } public static class MediaTranscodeManager.TranscodingRequest.MediaFormatResolver { @@ -71,5 +52,18 @@ package android.media { method public void onProgressUpdate(@NonNull android.media.MediaTranscodeManager.TranscodingSession, @IntRange(from=0, to=100) int); } + public static final class MediaTranscodeManager.VideoTranscodingRequest extends android.media.MediaTranscodeManager.TranscodingRequest { + method @NonNull public android.media.MediaFormat getVideoTrackFormat(); + } + + public static final class MediaTranscodeManager.VideoTranscodingRequest.Builder { + ctor public MediaTranscodeManager.VideoTranscodingRequest.Builder(@NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.media.MediaFormat); + method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest build(); + method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setClientPid(int); + method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setClientUid(int); + method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setDestinationFileDescriptor(android.os.ParcelFileDescriptor); + method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setSourceFileDescriptor(android.os.ParcelFileDescriptor); + } + } diff --git a/apex/media/framework/java/android/media/MediaTranscodeManager.java b/apex/media/framework/java/android/media/MediaTranscodeManager.java index 84332e5bbcf0..79e0d58cf495 100644 --- a/apex/media/framework/java/android/media/MediaTranscodeManager.java +++ b/apex/media/framework/java/android/media/MediaTranscodeManager.java @@ -50,27 +50,23 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** - MediaTranscodeManager provides an interface to the system's media transcoding service and can be - used to transcode media files, e.g. transcoding a video from HEVC to AVC. + Android 12 introduces Compatible media transcoding feature. See + <a href="https://developer.android.com/about/versions/12/features#compatible_media_transcoding"> + Compatible media transcoding</a>. MediaTranscodeManager provides an interface to the system's media + transcoding service and can be used to transcode media files, e.g. transcoding a video from HEVC to + AVC. <h3>Transcoding Types</h3> <h4>Video Transcoding</h4> - When transcoding a video file, the video file could be of any of the following types: - <ul> - <li> Video file with single video track. </li> - <li> Video file with multiple video track. </li> - <li> Video file with multiple video tracks and audio tracks. </li> - <li> Video file with video/audio tracks and metadata track. Note that metadata track will be passed - through only if it could be recognized by {@link MediaExtractor}. - TODO(hkuang): Finalize the metadata track behavior. </li> - </ul> + When transcoding a video file, the video track will be transcoded based on the desired track format + and the audio track will be pass through without any modification. <p class=note> - Note that currently only support transcoding video file in mp4 format. + Note that currently only support transcoding video file in mp4 format and with single video track. <h3>Transcoding Request</h3> <p> To transcode a media file, first create a {@link TranscodingRequest} through its builder class - {@link TranscodingRequest.Builder}. Transcode requests are then enqueue to the manager through + {@link VideoTranscodingRequest.Builder}. Transcode requests are then enqueue to the manager through {@link MediaTranscodeManager#enqueueRequest( TranscodingRequest, Executor, OnTranscodingFinishedListener)} TranscodeRequest are processed based on client process's priority and request priority. When a @@ -82,23 +78,9 @@ import java.util.concurrent.Executors; Here is an example where <code>Builder</code> is used to specify all parameters <pre class=prettyprint> - TranscodingRequest request = - new TranscodingRequest.Builder() - .setSourceUri(srcUri) - .setDestinationUri(dstUri) - .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO) - .setPriority(REALTIME) - .setVideoTrackFormat(videoFormat) - .build(); + VideoTranscodingRequest request = + new VideoTranscodingRequest.Builder(srcUri, dstUri, videoFormat).build(); }</pre> - - TODO(hkuang): Add architecture diagram showing the transcoding service and api. - TODO(hkuang): Add sample code when API is settled. - TODO(hkuang): Clarify whether multiple video tracks is supported or not. - TODO(hkuang): Clarify whether image/audio transcoding is supported or not. - TODO(hkuang): Clarify what will happen if there is unrecognized track in the source. - TODO(hkuang): Clarify whether supports scaling. - TODO(hkuang): Clarify whether supports framerate conversion. @hide */ @MinSdk(Build.VERSION_CODES.S) @@ -116,68 +98,6 @@ public final class MediaTranscodeManager { private static final float BPP = 0.25f; /** - * Default transcoding type. - * @hide - */ - public static final int TRANSCODING_TYPE_UNKNOWN = 0; - - /** - * TRANSCODING_TYPE_VIDEO indicates that client wants to perform transcoding on a video file. - * <p>Note that currently only support transcoding video file in mp4 format. - */ - public static final int TRANSCODING_TYPE_VIDEO = 1; - - /** - * TRANSCODING_TYPE_IMAGE indicates that client wants to perform transcoding on an image file. - * @hide - */ - public static final int TRANSCODING_TYPE_IMAGE = 2; - - /** @hide */ - @IntDef(prefix = {"TRANSCODING_TYPE_"}, value = { - TRANSCODING_TYPE_UNKNOWN, - TRANSCODING_TYPE_VIDEO, - TRANSCODING_TYPE_IMAGE, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface TranscodingType {} - - /** - * Default value. - * @hide - */ - public static final int PRIORITY_UNKNOWN = 0; - /** - * PRIORITY_REALTIME indicates that the transcoding request is time-critical and that the - * client wants the transcoding result as soon as possible. - * <p> Set PRIORITY_REALTIME only if the transcoding is time-critical as it will involve - * performance penalty due to resource reallocation to prioritize the sessions with higher - * priority. - * TODO(hkuang): Add more description of this when priority is finalized. - */ - public static final int PRIORITY_REALTIME = 1; - - /** - * PRIORITY_OFFLINE indicates the transcoding is not time-critical and the client does not need - * the transcoding result as soon as possible. - * <p>Sessions with PRIORITY_OFFLINE will be scheduled behind PRIORITY_REALTIME. Always set to - * PRIORITY_OFFLINE if client does not need the result as soon as possible and could accept - * delay of the transcoding result. - * @hide - * TODO(hkuang): Add more description of this when priority is finalized. - */ - public static final int PRIORITY_OFFLINE = 2; - - /** @hide */ - @IntDef(prefix = {"PRIORITY_"}, value = { - PRIORITY_UNKNOWN, - PRIORITY_REALTIME, - PRIORITY_OFFLINE, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface TranscodingPriority {} - - /** * Listener that gets notified when a transcoding operation has finished. * This listener gets notified regardless of how the operation finished. It is up to the * listener implementation to check the result and take appropriate action. @@ -503,7 +423,79 @@ public final class MediaTranscodeManager { } } - public static final class TranscodingRequest { + /** + * Abstract base class for all the TranscodingRequest. + * <p> TranscodingRequest encapsulates the desired configuration for the transcoding. + */ + public abstract static class TranscodingRequest { + /** + * + * Default transcoding type. + * @hide + */ + public static final int TRANSCODING_TYPE_UNKNOWN = 0; + + /** + * TRANSCODING_TYPE_VIDEO indicates that client wants to perform transcoding on a video. + * <p>Note that currently only support transcoding video file in mp4 format. + * @hide + */ + public static final int TRANSCODING_TYPE_VIDEO = 1; + + /** + * TRANSCODING_TYPE_IMAGE indicates that client wants to perform transcoding on an image. + * @hide + */ + public static final int TRANSCODING_TYPE_IMAGE = 2; + + /** @hide */ + @IntDef(prefix = {"TRANSCODING_TYPE_"}, value = { + TRANSCODING_TYPE_UNKNOWN, + TRANSCODING_TYPE_VIDEO, + TRANSCODING_TYPE_IMAGE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TranscodingType {} + + /** + * Default value. + * + * @hide + */ + public static final int PRIORITY_UNKNOWN = 0; + /** + * PRIORITY_REALTIME indicates that the transcoding request is time-critical and that the + * client wants the transcoding result as soon as possible. + * <p> Set PRIORITY_REALTIME only if the transcoding is time-critical as it will involve + * performance penalty due to resource reallocation to prioritize the sessions with higher + * priority. + * + * @hide + */ + public static final int PRIORITY_REALTIME = 1; + + /** + * PRIORITY_OFFLINE indicates the transcoding is not time-critical and the client does not + * need the transcoding result as soon as possible. + * <p>Sessions with PRIORITY_OFFLINE will be scheduled behind PRIORITY_REALTIME. Always set + * to + * PRIORITY_OFFLINE if client does not need the result as soon as possible and could accept + * delay of the transcoding result. + * + * @hide + * + */ + public static final int PRIORITY_OFFLINE = 2; + + /** @hide */ + @IntDef(prefix = {"PRIORITY_"}, value = { + PRIORITY_UNKNOWN, + PRIORITY_REALTIME, + PRIORITY_OFFLINE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TranscodingPriority {} + /** Uri of the source media file. */ private @NonNull Uri mSourceUri; @@ -537,22 +529,6 @@ public final class MediaTranscodeManager { private @TranscodingPriority int mPriority = PRIORITY_UNKNOWN; /** - * Desired output video format of the destination file. - * <p> If this is null, source file's video track will be passed through and copied to the - * destination file. - * <p> - */ - private @Nullable MediaFormat mVideoTrackFormat = null; - - /** - * Desired output audio format of the destination file. - * <p> If this is null, source file's audio track will be passed through and copied to the - * destination file. - * @hide - */ - private @Nullable MediaFormat mAudioTrackFormat = null; - - /** * Desired image format for the destination file. * <p> If this is null, source file's image track will be passed through and copied to the * destination file. @@ -563,6 +539,12 @@ public final class MediaTranscodeManager { @VisibleForTesting private TranscodingTestConfig mTestConfig = null; + /** + * Prevent public constructor access. + */ + /* package private */ TranscodingRequest() { + } + private TranscodingRequest(Builder b) { mSourceUri = b.mSourceUri; mSourceFileDescriptor = b.mSourceFileDescriptor; @@ -572,13 +554,13 @@ public final class MediaTranscodeManager { mClientPid = b.mClientPid; mPriority = b.mPriority; mType = b.mType; - mVideoTrackFormat = b.mVideoTrackFormat; - mAudioTrackFormat = b.mAudioTrackFormat; - mImageFormat = b.mImageFormat; mTestConfig = b.mTestConfig; } - /** Return the type of the transcoding. */ + /** + * Return the type of the transcoding. + * @hide + */ @TranscodingType public int getType() { return mType; @@ -624,22 +606,16 @@ public final class MediaTranscodeManager { return mDestinationFileDescriptor; } - /** Return priority of the transcoding. */ + /** + * Return priority of the transcoding. + * @hide + */ @TranscodingPriority public int getPriority() { return mPriority; } /** - * Return the video track format of the transcoding. - * This will be null is the transcoding is not for video transcoding. - */ - @Nullable - public MediaFormat getVideoTrackFormat() { - return mVideoTrackFormat; - } - - /** * Return TestConfig of the transcoding. * @hide */ @@ -648,6 +624,8 @@ public final class MediaTranscodeManager { return mTestConfig; } + abstract void writeFormatToParcel(TranscodingRequestParcel parcel); + /* Writes the TranscodingRequest to a parcel. */ private TranscodingRequestParcel writeToParcel(@NonNull Context context) { TranscodingRequestParcel parcel = new TranscodingRequestParcel(); @@ -671,7 +649,7 @@ public final class MediaTranscodeManager { } parcel.clientPackageName = packageName; } - parcel.requestedVideoTrackFormat = convertToVideoTrackFormat(mVideoTrackFormat); + writeFormatToParcel(parcel); if (mTestConfig != null) { parcel.isForTesting = true; parcel.testConfig = mTestConfig; @@ -679,71 +657,12 @@ public final class MediaTranscodeManager { return parcel; } - /* Converts the MediaFormat to TranscodingVideoTrackFormat. */ - private static TranscodingVideoTrackFormat convertToVideoTrackFormat(MediaFormat format) { - if (format == null) { - throw new IllegalArgumentException("Invalid MediaFormat"); - } - - TranscodingVideoTrackFormat trackFormat = new TranscodingVideoTrackFormat(); - - if (format.containsKey(MediaFormat.KEY_MIME)) { - String mime = format.getString(MediaFormat.KEY_MIME); - if (MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) { - trackFormat.codecType = TranscodingVideoCodecType.kAvc; - } else if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime)) { - trackFormat.codecType = TranscodingVideoCodecType.kHevc; - } else { - throw new UnsupportedOperationException("Only support transcode to avc/hevc"); - } - } - - if (format.containsKey(MediaFormat.KEY_BIT_RATE)) { - int bitrateBps = format.getInteger(MediaFormat.KEY_BIT_RATE); - if (bitrateBps <= 0) { - throw new IllegalArgumentException("Bitrate must be larger than 0"); - } - trackFormat.bitrateBps = bitrateBps; - } - - if (format.containsKey(MediaFormat.KEY_WIDTH) && format.containsKey( - MediaFormat.KEY_HEIGHT)) { - int width = format.getInteger(MediaFormat.KEY_WIDTH); - int height = format.getInteger(MediaFormat.KEY_HEIGHT); - if (width <= 0 || height <= 0) { - throw new IllegalArgumentException("Width and height must be larger than 0"); - } - // TODO(hkuang): Validate the aspect ratio after adding scaling. - trackFormat.width = width; - trackFormat.height = height; - } - - if (format.containsKey(MediaFormat.KEY_PROFILE)) { - int profile = format.getInteger(MediaFormat.KEY_PROFILE); - if (profile <= 0) { - throw new IllegalArgumentException("Invalid codec profile"); - } - // TODO(hkuang): Validate the profile according to codec type. - trackFormat.profile = profile; - } - - if (format.containsKey(MediaFormat.KEY_LEVEL)) { - int level = format.getInteger(MediaFormat.KEY_LEVEL); - if (level <= 0) { - throw new IllegalArgumentException("Invalid codec level"); - } - // TODO(hkuang): Validate the level according to codec type. - trackFormat.level = level; - } - - return trackFormat; - } - /** - * Builder class for {@link TranscodingRequest} objects. - * Use this class to configure and create a <code>TranscodingRequest</code> instance. + * Builder to build a {@link TranscodingRequest} object. + * + * @param <T> The subclass to be built. */ - public static final class Builder { + abstract static class Builder<T extends Builder<T>> { private @NonNull Uri mSourceUri; private @NonNull Uri mDestinationUri; private @Nullable ParcelFileDescriptor mSourceFileDescriptor = null; @@ -752,80 +671,68 @@ public final class MediaTranscodeManager { private int mClientPid = -1; private @TranscodingType int mType = TRANSCODING_TYPE_UNKNOWN; private @TranscodingPriority int mPriority = PRIORITY_UNKNOWN; - private @Nullable MediaFormat mVideoTrackFormat; - private @Nullable MediaFormat mAudioTrackFormat; - private @Nullable MediaFormat mImageFormat; private TranscodingTestConfig mTestConfig; + abstract T self(); + /** - * Specifies the uri of source media file. + * Creates a builder for building {@link TranscodingRequest}s. * * Client must set the source Uri. If client also provides the source fileDescriptor * through is provided by {@link #setSourceFileDescriptor(ParcelFileDescriptor)}, * TranscodingSession will use the fd instead of calling back to the client to open the * sourceUri. + * + * + * @param type The transcoding type. * @param sourceUri Content uri for the source media file. - * @return The same builder instance. - * @throws IllegalArgumentException if Uri is null or empty. + * @param destinationUri Content uri for the destination media file. + * */ - @NonNull - public Builder setSourceUri(@NonNull Uri sourceUri) { + private Builder(@TranscodingType int type, @NonNull Uri sourceUri, + @NonNull Uri destinationUri) { + mType = type; + if (sourceUri == null || Uri.EMPTY.equals(sourceUri)) { throw new IllegalArgumentException( "You must specify a non-empty source Uri."); } mSourceUri = sourceUri; - return this; + + if (destinationUri == null || Uri.EMPTY.equals(destinationUri)) { + throw new IllegalArgumentException( + "You must specify a non-empty destination Uri."); + } + mDestinationUri = destinationUri; } /** * Specifies the fileDescriptor opened from the source media file. * * This call is optional. If the source fileDescriptor is provided, TranscodingSession - * will use it directly instead of opening the uri from {@link #setSourceUri(Uri)}. It - * is client's responsibility to make sure the fileDescriptor is opened from the source - * uri. + * will use it directly instead of opening the uri from {@link #Builder(int, Uri, Uri)}. + * It is client's responsibility to make sure the fileDescriptor is opened from the + * source uri. * @param fileDescriptor a {@link ParcelFileDescriptor} opened from source media file. * @return The same builder instance. * @throws IllegalArgumentException if fileDescriptor is invalid. */ @NonNull - public Builder setSourceFileDescriptor(@NonNull ParcelFileDescriptor fileDescriptor) { + public T setSourceFileDescriptor(@NonNull ParcelFileDescriptor fileDescriptor) { if (fileDescriptor == null || fileDescriptor.getFd() < 0) { throw new IllegalArgumentException( "Invalid source descriptor."); } mSourceFileDescriptor = fileDescriptor; - return this; - } - - /** - * Specifies the uri of the destination media file. - * - * Client must set the destination Uri. If client also provides the destination - * fileDescriptor through {@link #setDestinationFileDescriptor(ParcelFileDescriptor)}, - * TranscodingSession will use the fd instead of calling back to the client to open the - * destinationUri. - * @param destinationUri Content uri for the destination media file. - * @return The same builder instance. - * @throws IllegalArgumentException if Uri is null or empty. - */ - @NonNull - public Builder setDestinationUri(@NonNull Uri destinationUri) { - if (destinationUri == null || Uri.EMPTY.equals(destinationUri)) { - throw new IllegalArgumentException( - "You must specify a non-empty destination Uri."); - } - mDestinationUri = destinationUri; - return this; + return self(); } /** * Specifies the fileDescriptor opened from the destination media file. * * This call is optional. If the destination fileDescriptor is provided, - * TranscodingSession will use it directly instead of opening the uri from - * {@link #setDestinationUri(Uri)} upon transcoding starts. It is client's + * TranscodingSession will use it directly instead of opening the source uri from + * {@link #Builder(int, Uri, Uri)} upon transcoding starts. It is client's * responsibility to make sure the fileDescriptor is opened from the destination uri. * @param fileDescriptor a {@link ParcelFileDescriptor} opened from destination media * file. @@ -833,46 +740,54 @@ public final class MediaTranscodeManager { * @throws IllegalArgumentException if fileDescriptor is invalid. */ @NonNull - public Builder setDestinationFileDescriptor( + public T setDestinationFileDescriptor( @NonNull ParcelFileDescriptor fileDescriptor) { if (fileDescriptor == null || fileDescriptor.getFd() < 0) { throw new IllegalArgumentException( "Invalid destination descriptor."); } mDestinationFileDescriptor = fileDescriptor; - return this; + return self(); } /** * Specify the UID of the client that this request is for. + * <p> + * Only privilege caller with android.permission.WRITE_MEDIA_STORAGE could forward the + * pid. Note that the permission check happens on the service side upon starting the + * transcoding. If the client does not have the permission, the transcoding will fail. + * * @param uid client Uid. * @return The same builder instance. * @throws IllegalArgumentException if uid is invalid. - * TODO(hkuang): Check the permission if it is allowed. */ @NonNull - public Builder setClientUid(int uid) { + public T setClientUid(int uid) { if (uid < 0) { throw new IllegalArgumentException("Invalid Uid"); } mClientUid = uid; - return this; + return self(); } /** - * Specify the PID of the client that this request is for. + * Specify the pid of the client that this request is for. + * <p> + * Only privilege caller with android.permission.WRITE_MEDIA_STORAGE could forward the + * pid. Note that the permission check happens on the service side upon starting the + * transcoding. If the client does not have the permission, the transcoding will fail. + * * @param pid client Pid. * @return The same builder instance. * @throws IllegalArgumentException if pid is invalid. - * TODO(hkuang): Check the permission if it is allowed. */ @NonNull - public Builder setClientPid(int pid) { + public T setClientPid(int pid) { if (pid < 0) { throw new IllegalArgumentException("Invalid pid"); } mClientPid = pid; - return this; + return self(); } /** @@ -881,64 +796,15 @@ public final class MediaTranscodeManager { * @param priority Must be one of the {@code PRIORITY_*} * @return The same builder instance. * @throws IllegalArgumentException if flags is invalid. + * @hide */ @NonNull - public Builder setPriority(@TranscodingPriority int priority) { + public T setPriority(@TranscodingPriority int priority) { if (priority != PRIORITY_OFFLINE && priority != PRIORITY_REALTIME) { throw new IllegalArgumentException("Invalid priority: " + priority); } mPriority = priority; - return this; - } - - /** - * Specifies the type of transcoding. - * <p> Clients must provide the source and destination that corresponds to the - * transcoding type. - * - * @param type Must be one of the {@code TRANSCODING_TYPE_*} - * @return The same builder instance. - * @throws IllegalArgumentException if flags is invalid. - */ - @NonNull - public Builder setType(@TranscodingType int type) { - if (type != TRANSCODING_TYPE_VIDEO && type != TRANSCODING_TYPE_IMAGE) { - throw new IllegalArgumentException("Invalid transcoding type"); - } - mType = type; - return this; - } - - /** - * Specifies the desired video track format in the destination media file. - * <p>Client could only specify the settings that matters to them, e.g. codec format or - * bitrate. And by default, transcoding will preserve the original video's - * settings(bitrate, framerate, resolution) if not provided. - * <p>Note that some settings may silently fail to apply if the device does not - * support them. - * TODO(hkuang): Add MediaTranscodeUtil to help client generate transcoding setting. - * TODO(hkuang): Add MediaTranscodeUtil to check if the setting is valid. - * - * @param videoFormat MediaFormat containing the settings that client wants override in - * the original video's video track. - * @return The same builder instance. - * @throws IllegalArgumentException if videoFormat is invalid. - */ - @NonNull - public Builder setVideoTrackFormat(@NonNull MediaFormat videoFormat) { - if (videoFormat == null) { - throw new IllegalArgumentException("videoFormat must not be null"); - } - - // Check if the MediaFormat is for video by looking at the MIME type. - String mime = videoFormat.containsKey(MediaFormat.KEY_MIME) - ? videoFormat.getString(MediaFormat.KEY_MIME) : null; - if (mime == null || !mime.startsWith("video/")) { - throw new IllegalArgumentException("Invalid video format: wrong mime type"); - } - - mVideoTrackFormat = videoFormat; - return this; + return self(); } /** @@ -949,44 +815,9 @@ public final class MediaTranscodeManager { */ @VisibleForTesting @NonNull - public Builder setTestConfig(@NonNull TranscodingTestConfig config) { + public T setTestConfig(@NonNull TranscodingTestConfig config) { mTestConfig = config; - return this; - } - - /** - * @return a new {@link TranscodingRequest} instance successfully initialized with all - * the parameters set on this <code>Builder</code>. - * @throws UnsupportedOperationException if the parameters set on the - * <code>Builder</code> were incompatible, or if they are not supported by the - * device. - */ - @NonNull - public TranscodingRequest build() { - if (mSourceUri == null) { - throw new UnsupportedOperationException("Source URI must not be null"); - } - - if (mDestinationUri == null) { - throw new UnsupportedOperationException("Destination URI must not be null"); - } - - if (mPriority == PRIORITY_UNKNOWN) { - throw new UnsupportedOperationException("Must specify transcoding priority"); - } - - // Only support video transcoding now. - if (mType != TRANSCODING_TYPE_VIDEO) { - throw new UnsupportedOperationException("Only supports video transcoding now"); - } - - // Must provide video track format for video transcoding. - if (mType == TRANSCODING_TYPE_VIDEO && mVideoTrackFormat == null) { - throw new UnsupportedOperationException( - "Must provide video track format for video transcoding"); - } - - return new TranscodingRequest(this); + return self(); } } @@ -1201,6 +1032,206 @@ public final class MediaTranscodeManager { } /** + * VideoTranscodingRequest encapsulates the configuration for transcoding a video. + */ + public static final class VideoTranscodingRequest extends TranscodingRequest { + /** + * Desired output video format of the destination file. + * <p> If this is null, source file's video track will be passed through and copied to the + * destination file. + */ + private @Nullable MediaFormat mVideoTrackFormat = null; + + /** + * Desired output audio format of the destination file. + * <p> If this is null, source file's audio track will be passed through and copied to the + * destination file. + */ + private @Nullable MediaFormat mAudioTrackFormat = null; + + private VideoTranscodingRequest(VideoTranscodingRequest.Builder builder) { + super(builder); + mVideoTrackFormat = builder.mVideoTrackFormat; + mAudioTrackFormat = builder.mAudioTrackFormat; + } + + /** + * Return the video track format of the transcoding. + * This will be null if client has not specified the video track format. + */ + @NonNull + public MediaFormat getVideoTrackFormat() { + return mVideoTrackFormat; + } + + @Override + void writeFormatToParcel(TranscodingRequestParcel parcel) { + parcel.requestedVideoTrackFormat = convertToVideoTrackFormat(mVideoTrackFormat); + } + + /* Converts the MediaFormat to TranscodingVideoTrackFormat. */ + private static TranscodingVideoTrackFormat convertToVideoTrackFormat(MediaFormat format) { + if (format == null) { + throw new IllegalArgumentException("Invalid MediaFormat"); + } + + TranscodingVideoTrackFormat trackFormat = new TranscodingVideoTrackFormat(); + + if (format.containsKey(MediaFormat.KEY_MIME)) { + String mime = format.getString(MediaFormat.KEY_MIME); + if (MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) { + trackFormat.codecType = TranscodingVideoCodecType.kAvc; + } else if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime)) { + trackFormat.codecType = TranscodingVideoCodecType.kHevc; + } else { + throw new UnsupportedOperationException("Only support transcode to avc/hevc"); + } + } + + if (format.containsKey(MediaFormat.KEY_BIT_RATE)) { + int bitrateBps = format.getInteger(MediaFormat.KEY_BIT_RATE); + if (bitrateBps <= 0) { + throw new IllegalArgumentException("Bitrate must be larger than 0"); + } + trackFormat.bitrateBps = bitrateBps; + } + + if (format.containsKey(MediaFormat.KEY_WIDTH) && format.containsKey( + MediaFormat.KEY_HEIGHT)) { + int width = format.getInteger(MediaFormat.KEY_WIDTH); + int height = format.getInteger(MediaFormat.KEY_HEIGHT); + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Width and height must be larger than 0"); + } + // TODO: Validate the aspect ratio after adding scaling. + trackFormat.width = width; + trackFormat.height = height; + } + + if (format.containsKey(MediaFormat.KEY_PROFILE)) { + int profile = format.getInteger(MediaFormat.KEY_PROFILE); + if (profile <= 0) { + throw new IllegalArgumentException("Invalid codec profile"); + } + // TODO: Validate the profile according to codec type. + trackFormat.profile = profile; + } + + if (format.containsKey(MediaFormat.KEY_LEVEL)) { + int level = format.getInteger(MediaFormat.KEY_LEVEL); + if (level <= 0) { + throw new IllegalArgumentException("Invalid codec level"); + } + // TODO: Validate the level according to codec type. + trackFormat.level = level; + } + + return trackFormat; + } + + /** + * Builder class for {@link VideoTranscodingRequest}. + */ + public static final class Builder extends + TranscodingRequest.Builder<VideoTranscodingRequest.Builder> { + /** + * Desired output video format of the destination file. + * <p> If this is null, source file's video track will be passed through and + * copied to the destination file. + */ + private @Nullable MediaFormat mVideoTrackFormat = null; + + /** + * Desired output audio format of the destination file. + * <p> If this is null, source file's audio track will be passed through and copied + * to the destination file. + */ + private @Nullable MediaFormat mAudioTrackFormat = null; + + /** + * Creates a builder for building {@link VideoTranscodingRequest}s. + * + * <p> Client could only specify the settings that matters to them, e.g. codec format or + * bitrate. And by default, transcoding will preserve the original video's settings + * (bitrate, framerate, resolution) if not provided. + * <p>Note that some settings may silently fail to apply if the device does not support + * them. + * @param sourceUri Content uri for the source media file. + * @param destinationUri Content uri for the destination media file. + * @param videoFormat MediaFormat containing the settings that client wants override in + * the original video's video track. + * @throws IllegalArgumentException if videoFormat is invalid. + */ + public Builder(@NonNull Uri sourceUri, @NonNull Uri destinationUri, + @NonNull MediaFormat videoFormat) { + super(TRANSCODING_TYPE_VIDEO, sourceUri, destinationUri); + setVideoTrackFormat(videoFormat); + } + + @Override + @NonNull + public Builder setClientUid(int uid) { + super.setClientUid(uid); + return self(); + } + + @Override + @NonNull + public Builder setClientPid(int pid) { + super.setClientPid(pid); + return self(); + } + + @Override + @NonNull + public Builder setSourceFileDescriptor(ParcelFileDescriptor fd) { + super.setSourceFileDescriptor(fd); + return self(); + } + + @Override + @NonNull + public Builder setDestinationFileDescriptor(ParcelFileDescriptor fd) { + super.setDestinationFileDescriptor(fd); + return self(); + } + + private void setVideoTrackFormat(@NonNull MediaFormat videoFormat) { + if (videoFormat == null) { + throw new IllegalArgumentException("videoFormat must not be null"); + } + + // Check if the MediaFormat is for video by looking at the MIME type. + String mime = videoFormat.containsKey(MediaFormat.KEY_MIME) + ? videoFormat.getString(MediaFormat.KEY_MIME) : null; + if (mime == null || !mime.startsWith("video/")) { + throw new IllegalArgumentException("Invalid video format: wrong mime type"); + } + + mVideoTrackFormat = videoFormat; + } + + /** + * @return a new {@link TranscodingRequest} instance successfully initialized + * with all the parameters set on this <code>Builder</code>. + * @throws UnsupportedOperationException if the parameters set on the + * <code>Builder</code> were incompatible, or + * if they are not supported by the + * device. + */ + @NonNull + public VideoTranscodingRequest build() { + return new VideoTranscodingRequest(this); + } + + @Override + VideoTranscodingRequest.Builder self() { + return this; + } + } + } + + /** * Handle to an enqueued transcoding operation. An instance of this class represents a single * enqueued transcoding operation. The caller can use that instance to query the status or * progress, and to get the result once the operation has completed. diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp index 4e5b3bac5713..0eff83c99282 100644 --- a/cmds/app_process/Android.bp +++ b/cmds/app_process/Android.bp @@ -22,13 +22,9 @@ cc_binary { multilib: { lib32: { - // TODO(b/142944043): Remove version script when libsigchain is a DSO. - version_script: "version-script32.txt", suffix: "32", }, lib64: { - // TODO(b/142944043): Remove version script when libsigchain is a DSO. - version_script: "version-script64.txt", suffix: "64", }, }, @@ -43,6 +39,13 @@ cc_binary { "libhidlbase", "liblog", "libnativeloader", + + // Even though app_process doesn't call into libsigchain, we need to + // make sure it's in the DT list of app_process, as we want all code + // in app_process and the libraries it loads to find libsigchain + // symbols before libc symbols. + "libsigchain", + "libutils", // This is a list of libraries that need to be included in order to avoid @@ -52,8 +55,6 @@ cc_binary { "libwilhelm", ], - whole_static_libs: ["libsigchain"], - compile_multilib: "both", cflags: [ diff --git a/cmds/app_process/version-script32.txt b/cmds/app_process/version-script32.txt deleted file mode 100644 index 70810e0b7173..000000000000 --- a/cmds/app_process/version-script32.txt +++ /dev/null @@ -1,15 +0,0 @@ -{ -global: - EnsureFrontOfChain; - AddSpecialSignalHandlerFn; - RemoveSpecialSignalHandlerFn; - SkipAddSignalHandler; - bsd_signal; - sigaction; - sigaction64; - signal; - sigprocmask; - sigprocmask64; -local: - *; -}; diff --git a/cmds/app_process/version-script64.txt b/cmds/app_process/version-script64.txt deleted file mode 100644 index 7bcd76b50f87..000000000000 --- a/cmds/app_process/version-script64.txt +++ /dev/null @@ -1,14 +0,0 @@ -{ -global: - EnsureFrontOfChain; - AddSpecialSignalHandlerFn; - RemoveSpecialSignalHandlerFn; - SkipAddSignalHandler; - sigaction; - sigaction64; - signal; - sigprocmask; - sigprocmask64; -local: - *; -}; diff --git a/core/api/current.txt b/core/api/current.txt index 2b5196b3b5f7..f23483c26207 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8257,13 +8257,13 @@ package android.app.usage { } public class NetworkStatsManager { - method public android.app.usage.NetworkStats queryDetails(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; - method public android.app.usage.NetworkStats queryDetailsForUid(int, String, long, long, int) throws java.lang.SecurityException; - method public android.app.usage.NetworkStats queryDetailsForUidTag(int, String, long, long, int, int) throws java.lang.SecurityException; - method public android.app.usage.NetworkStats queryDetailsForUidTagState(int, String, long, long, int, int, int) throws java.lang.SecurityException; - method public android.app.usage.NetworkStats querySummary(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; - method public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; - method public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; + method @WorkerThread public android.app.usage.NetworkStats queryDetails(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; + method @WorkerThread public android.app.usage.NetworkStats queryDetailsForUid(int, String, long, long, int) throws java.lang.SecurityException; + method @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTag(int, String, long, long, int, int) throws java.lang.SecurityException; + method @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTagState(int, String, long, long, int, int, int) throws java.lang.SecurityException; + method @WorkerThread public android.app.usage.NetworkStats querySummary(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; + method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; + method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; method public void registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback); method public void registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback, @Nullable android.os.Handler); method public void unregisterUsageCallback(android.app.usage.NetworkStatsManager.UsageCallback); @@ -18438,12 +18438,12 @@ package android.hardware.camera2 { } public class MultiResolutionImageReader implements java.lang.AutoCloseable { + ctor public MultiResolutionImageReader(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int, @IntRange(from=1) int); method public void close(); method protected void finalize(); method public void flush(); method @NonNull public android.hardware.camera2.params.MultiResolutionStreamInfo getStreamInfoForImageReader(@NonNull android.media.ImageReader); method @NonNull public android.view.Surface getSurface(); - method @NonNull public static android.hardware.camera2.MultiResolutionImageReader newInstance(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int, @IntRange(from=1) int); method public void setOnImageAvailableListener(@Nullable android.media.ImageReader.OnImageAvailableListener, @Nullable java.util.concurrent.Executor); } @@ -18554,10 +18554,10 @@ package android.hardware.camera2.params { } public class MultiResolutionStreamInfo { - ctor public MultiResolutionStreamInfo(int, int, @NonNull String); - method public int getHeight(); + ctor public MultiResolutionStreamInfo(@IntRange(from=1) int, @IntRange(from=1) int, @NonNull String); + method @IntRange(from=1) public int getHeight(); method @NonNull public String getPhysicalCameraId(); - method public int getWidth(); + method @IntRange(from=1) public int getWidth(); } public final class OisSample { @@ -22318,6 +22318,8 @@ package android.media { field public static final String MIMETYPE_AUDIO_G711_ALAW = "audio/g711-alaw"; field public static final String MIMETYPE_AUDIO_G711_MLAW = "audio/g711-mlaw"; field public static final String MIMETYPE_AUDIO_MPEG = "audio/mpeg"; + field public static final String MIMETYPE_AUDIO_MPEGH_MHA1 = "audio/mha1"; + field public static final String MIMETYPE_AUDIO_MPEGH_MHM1 = "audio/mhm1"; field public static final String MIMETYPE_AUDIO_MSGSM = "audio/gsm"; field public static final String MIMETYPE_AUDIO_OPUS = "audio/opus"; field public static final String MIMETYPE_AUDIO_QCELP = "audio/qcelp"; @@ -37399,12 +37401,15 @@ package android.service.autofill { method @NonNull public android.service.autofill.Dataset.Builder setAuthentication(@Nullable android.content.IntentSender); method @NonNull public android.service.autofill.Dataset.Builder setId(@Nullable String); method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation); + method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation); method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue); method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews); method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern); method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews); method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation); + method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation); method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation); + method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation); } public final class DateTransformation implements android.os.Parcelable android.service.autofill.Transformation { @@ -37505,6 +37510,7 @@ package android.service.autofill { method @NonNull public android.service.autofill.FillResponse.Builder disableAutofill(long); method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews); method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation); + method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation, @Nullable android.service.autofill.InlinePresentation); method @NonNull public android.service.autofill.FillResponse.Builder setClientState(@Nullable android.os.Bundle); method @NonNull public android.service.autofill.FillResponse.Builder setFieldClassificationIds(@NonNull android.view.autofill.AutofillId...); method @NonNull public android.service.autofill.FillResponse.Builder setFlags(int); @@ -37532,6 +37538,7 @@ package android.service.autofill { public final class InlinePresentation implements android.os.Parcelable { ctor public InlinePresentation(@NonNull android.app.slice.Slice, @NonNull android.widget.inline.InlinePresentationSpec, boolean); + method @NonNull public static android.service.autofill.InlinePresentation createTooltipPresentation(@NonNull android.app.slice.Slice, @NonNull android.widget.inline.InlinePresentationSpec); method public int describeContents(); method @NonNull public android.widget.inline.InlinePresentationSpec getInlinePresentationSpec(); method @NonNull public android.app.slice.Slice getSlice(); @@ -40528,6 +40535,7 @@ package android.telephony { field public static final String KEY_RTT_SUPPORTED_FOR_VT_BOOL = "rtt_supported_for_vt_bool"; field public static final String KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL = "rtt_supported_while_roaming_bool"; field public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool"; + field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call"; field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool"; field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool"; field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool"; @@ -40573,6 +40581,7 @@ package android.telephony { field public static final String KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL = "voicemail_notification_persistent_bool"; field public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool"; field public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int"; + field public static final String KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL = "vt_upgrade_supported_for_downgraded_rtt_call"; field public static final String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool"; field public static final String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string"; field public static final String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string"; @@ -42181,6 +42190,10 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onMessageWaitingIndicatorChanged(boolean); } + public static interface TelephonyCallback.PhysicalChannelConfigListener { + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPhysicalChannelConfigChanged(@NonNull java.util.List<android.telephony.PhysicalChannelConfig>); + } + public static interface TelephonyCallback.PreciseDataConnectionStateListener { method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState); } @@ -51328,6 +51341,7 @@ package android.view.inputmethod { method @NonNull public android.os.Bundle getExtras(); method @NonNull public String getHostPackageName(); method @NonNull public java.util.List<android.widget.inline.InlinePresentationSpec> getInlinePresentationSpecs(); + method @Nullable public android.widget.inline.InlinePresentationSpec getInlineTooltipPresentationSpec(); method public int getMaxSuggestionCount(); method @NonNull public android.os.LocaleList getSupportedLocales(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -51339,9 +51353,12 @@ package android.view.inputmethod { ctor public InlineSuggestionsRequest.Builder(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder addInlinePresentationSpecs(@NonNull android.widget.inline.InlinePresentationSpec); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest build(); + method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setClientSupported(boolean); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@NonNull android.os.Bundle); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>); + method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlineTooltipPresentationSpec(@NonNull android.widget.inline.InlinePresentationSpec); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setMaxSuggestionCount(int); + method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setServiceSupported(boolean); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setSupportedLocales(@NonNull android.os.LocaleList); } @@ -52403,6 +52420,17 @@ package android.view.translation { method @Nullable @WorkerThread public android.view.translation.TranslationResponse translate(@NonNull android.view.translation.TranslationRequest); } + public final class UiTranslationManager { + method public void registerUiTranslationStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.translation.UiTranslationStateCallback); + method public void unregisterUiTranslationStateCallback(@NonNull android.view.translation.UiTranslationStateCallback); + } + + public interface UiTranslationStateCallback { + method public void onFinished(); + method public void onPaused(); + method public void onStarted(@NonNull String, @NonNull String); + } + public final class ViewTranslationRequest implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.view.autofill.AutofillId getAutofillId(); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index a140e8ad095e..bc04815158ff 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -61,6 +61,7 @@ package android.content { public abstract class Context { method @NonNull public android.os.UserHandle getUser(); + field public static final String TEST_NETWORK_SERVICE = "test_network"; } public class Intent implements java.lang.Cloneable android.os.Parcelable { @@ -191,6 +192,25 @@ package android.net { method public int getResourceId(); } + public class NetworkPolicyManager { + method @NonNull public static String blockedReasonsToString(int); + method public static boolean isUidBlocked(int, boolean); + method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void registerNetworkPolicyCallback(@Nullable java.util.concurrent.Executor, @NonNull android.net.NetworkPolicyManager.NetworkPolicyCallback); + method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void unregisterNetworkPolicyCallback(@NonNull android.net.NetworkPolicyManager.NetworkPolicyCallback); + field public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 262144; // 0x40000 + field public static final int BLOCKED_METERED_REASON_DATA_SAVER = 65536; // 0x10000 + field public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 131072; // 0x20000 + field public static final int BLOCKED_REASON_APP_STANDBY = 4; // 0x4 + field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1 + field public static final int BLOCKED_REASON_DOZE = 2; // 0x2 + field public static final int BLOCKED_REASON_NONE = 0; // 0x0 + field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8 + } + + public static interface NetworkPolicyManager.NetworkPolicyCallback { + method public default void onUidBlockedReasonChanged(int, int); + } + public final class NetworkStateSnapshot implements android.os.Parcelable { ctor public NetworkStateSnapshot(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, @Nullable String, int); method public int describeContents(); @@ -258,6 +278,10 @@ package android.os { method public default int getStability(); } + public class Process { + field public static final int VPN_UID = 1016; // 0x3f8 + } + public class StatsServiceManager { method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsCompanionServiceRegisterer(); method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsManagerServiceRegisterer(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 54c666314f98..831ac6f0f107 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -136,6 +136,7 @@ package android { field public static final String MANAGE_CONTENT_SUGGESTIONS = "android.permission.MANAGE_CONTENT_SUGGESTIONS"; field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING"; field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION"; + field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION"; field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS"; field public static final String MANAGE_MUSIC_RECOGNITION = "android.permission.MANAGE_MUSIC_RECOGNITION"; field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS"; @@ -407,7 +408,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public static void setPersistentVrThread(int); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public boolean startProfile(@NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public boolean stopProfile(@NonNull android.os.UserHandle); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean switchUser(@NonNull android.os.UserHandle); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean switchUser(@NonNull android.os.UserHandle); } public static interface ActivityManager.OnUidImportanceListener { @@ -1695,6 +1696,13 @@ package android.app.smartspace { package android.app.time { + public final class Capabilities { + field public static final int CAPABILITY_NOT_ALLOWED = 20; // 0x14 + field public static final int CAPABILITY_NOT_APPLICABLE = 30; // 0x1e + field public static final int CAPABILITY_NOT_SUPPORTED = 10; // 0xa + field public static final int CAPABILITY_POSSESSED = 40; // 0x28 + } + public final class TimeManager { method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public void addTimeZoneDetectorListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.time.TimeManager.TimeZoneDetectorListener); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public android.app.time.TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig(); @@ -1711,10 +1719,6 @@ package android.app.time { method public int getConfigureAutoDetectionEnabledCapability(); method public int getConfigureGeoDetectionEnabledCapability(); method public void writeToParcel(@NonNull android.os.Parcel, int); - field public static final int CAPABILITY_NOT_ALLOWED = 20; // 0x14 - field public static final int CAPABILITY_NOT_APPLICABLE = 30; // 0x1e - field public static final int CAPABILITY_NOT_SUPPORTED = 10; // 0xa - field public static final int CAPABILITY_POSSESSED = 40; // 0x28 field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeZoneCapabilities> CREATOR; } @@ -2837,21 +2841,29 @@ package android.content.pm.verify.domain { method @NonNull public String getPackageName(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationInfo> CREATOR; + field public static final int STATE_FIRST_VERIFIER_DEFINED = 1024; // 0x400 + field public static final int STATE_MODIFIABLE_UNVERIFIED = 3; // 0x3 + field public static final int STATE_MODIFIABLE_VERIFIED = 4; // 0x4 + field public static final int STATE_NO_RESPONSE = 0; // 0x0 + field public static final int STATE_SUCCESS = 1; // 0x1 + field public static final int STATE_UNMODIFIABLE = 2; // 0x2 } public final class DomainVerificationManager { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String); - method public static boolean isStateModifiable(int); - method public static boolean isStateVerified(int); method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> queryValidVerificationPackageNames(); method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationLinkHandlingAllowed(@NonNull String, boolean) throws android.content.pm.PackageManager.NameNotFoundException; - method @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public void setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException; - method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException; + method @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public int setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public int setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException; + field public static final int ERROR_DOMAIN_SET_ID_INVALID = 1; // 0x1 + field public static final int ERROR_DOMAIN_SET_ID_NULL = 2; // 0x2 + field public static final int ERROR_DOMAIN_SET_NULL_OR_EMPTY = 3; // 0x3 + field public static final int ERROR_INVALID_STATE_CODE = 6; // 0x6 + field public static final int ERROR_UNABLE_TO_APPROVE = 5; // 0x5 + field public static final int ERROR_UNKNOWN_DOMAIN = 4; // 0x4 field public static final String EXTRA_VERIFICATION_REQUEST = "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST"; - field public static final int STATE_FIRST_VERIFIER_DEFINED = 1024; // 0x400 - field public static final int STATE_NO_RESPONSE = 0; // 0x0 - field public static final int STATE_SUCCESS = 1; // 0x1 + field public static final int STATUS_OK = 0; // 0x0 } public final class DomainVerificationRequest implements android.os.Parcelable { @@ -5050,8 +5062,8 @@ package android.media { method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies(); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups(); - method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes); - method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getDevicesForAttributes(@NonNull android.media.AudioAttributes); + method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public java.util.List<android.media.AudioDeviceAttributes> getDevicesForAttributes(@NonNull android.media.AudioAttributes); method @IntRange(from=0) public long getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); @@ -10347,7 +10359,7 @@ package android.service.voice { public class VoiceInteractionService extends android.app.Service { method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback); - method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, @Nullable android.os.Bundle, @Nullable android.os.SharedMemory, android.service.voice.AlwaysOnHotwordDetector.Callback); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, @Nullable android.os.Bundle, @Nullable android.os.SharedMemory, android.service.voice.AlwaysOnHotwordDetector.Callback); method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager(); } @@ -10452,11 +10464,32 @@ package android.telecom { method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onBluetoothCallQualityReportReceived(@NonNull android.telecom.BluetoothCallQualityReport); method public abstract void onCallAudioStateChanged(@NonNull android.telecom.CallAudioState); - method @NonNull public abstract android.telecom.DiagnosticCall onInitializeDiagnosticCall(@NonNull android.telecom.Call.Details); - method public abstract void onRemoveDiagnosticCall(@NonNull android.telecom.DiagnosticCall); + method @NonNull public abstract android.telecom.CallDiagnostics onInitializeCallDiagnostics(@NonNull android.telecom.Call.Details); + method public abstract void onRemoveCallDiagnostics(@NonNull android.telecom.CallDiagnostics); field public static final String SERVICE_INTERFACE = "android.telecom.CallDiagnosticService"; } + public abstract class CallDiagnostics { + ctor public CallDiagnostics(); + method public final void clearDiagnosticMessage(int); + method public final void displayDiagnosticMessage(int, @NonNull CharSequence); + method public abstract void onCallDetailsChanged(@NonNull android.telecom.Call.Details); + method @Nullable public abstract CharSequence onCallDisconnected(int, int); + method @Nullable public abstract CharSequence onCallDisconnected(@NonNull android.telephony.ims.ImsReasonInfo); + method public abstract void onCallQualityReceived(@NonNull android.telephony.CallQuality); + method public abstract void onReceiveDeviceToDeviceMessage(int, int); + method public final void sendDeviceToDeviceMessage(int, int); + field public static final int BATTERY_STATE_CHARGING = 3; // 0x3 + field public static final int BATTERY_STATE_GOOD = 2; // 0x2 + field public static final int BATTERY_STATE_LOW = 1; // 0x1 + field public static final int COVERAGE_GOOD = 2; // 0x2 + field public static final int COVERAGE_POOR = 1; // 0x1 + field public static final int MESSAGE_CALL_AUDIO_CODEC = 2; // 0x2 + field public static final int MESSAGE_CALL_NETWORK_TYPE = 1; // 0x1 + field public static final int MESSAGE_DEVICE_BATTERY_STATE = 3; // 0x3 + field public static final int MESSAGE_DEVICE_NETWORK_COVERAGE = 4; // 0x4 + } + public static class CallScreeningService.CallResponse.Builder { method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public android.telecom.CallScreeningService.CallResponse.Builder setShouldScreenCallViaAudioProcessing(boolean); } @@ -10517,25 +10550,8 @@ package android.telecom { method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference); } - public abstract class DiagnosticCall { - ctor public DiagnosticCall(); - method public final void clearDiagnosticMessage(int); - method public final void displayDiagnosticMessage(int, @NonNull CharSequence); - method public abstract void onCallDetailsChanged(@NonNull android.telecom.Call.Details); - method @Nullable public abstract CharSequence onCallDisconnected(int, int); - method @Nullable public abstract CharSequence onCallDisconnected(@NonNull android.telephony.ims.ImsReasonInfo); - method public abstract void onCallQualityReceived(@NonNull android.telephony.CallQuality); - method public abstract void onReceiveDeviceToDeviceMessage(int, int); - method public final void sendDeviceToDeviceMessage(int, int); - field public static final int BATTERY_STATE_CHARGING = 3; // 0x3 - field public static final int BATTERY_STATE_GOOD = 2; // 0x2 - field public static final int BATTERY_STATE_LOW = 1; // 0x1 - field public static final int COVERAGE_GOOD = 2; // 0x2 - field public static final int COVERAGE_POOR = 1; // 0x1 - field public static final int MESSAGE_CALL_AUDIO_CODEC = 2; // 0x2 - field public static final int MESSAGE_CALL_NETWORK_TYPE = 1; // 0x1 - field public static final int MESSAGE_DEVICE_BATTERY_STATE = 3; // 0x3 - field public static final int MESSAGE_DEVICE_NETWORK_COVERAGE = 4; // 0x4 + @Deprecated public abstract class DiagnosticCall extends android.telecom.CallDiagnostics { + ctor @Deprecated public DiagnosticCall(); } public abstract class InCallService extends android.app.Service { @@ -11605,10 +11621,6 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onPhoneCapabilityChanged(@NonNull android.telephony.PhoneCapability); } - public static interface TelephonyCallback.PhysicalChannelConfigListener { - method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPhysicalChannelConfigChanged(@NonNull java.util.List<android.telephony.PhysicalChannelConfig>); - } - public static interface TelephonyCallback.PreciseCallStateListener { method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index e486fa2c2e29..3e0094aec6f9 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -104,7 +104,9 @@ package android.app { method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors(); method public static void resumeAppSwitches() throws android.os.RemoteException; method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int); + method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean); method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String); + method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle(); field public static final long DROP_CLOSE_SYSTEM_DIALOGS = 174664120L; // 0xa6929b8L field public static final long LOCK_DOWN_CLOSE_SYSTEM_DIALOGS = 174664365L; // 0xa692aadL field public static final int PROCESS_CAPABILITY_ALL = 15; // 0xf @@ -709,6 +711,10 @@ package android.content { method public int getDisplayId(); } + public class Intent implements java.lang.Cloneable android.os.Parcelable { + field public static final String ACTION_USER_STOPPED = "android.intent.action.USER_STOPPED"; + } + public class SyncAdapterType implements android.os.Parcelable { method @Nullable public String getPackageName(); } @@ -1329,6 +1335,7 @@ package android.media { public class AudioManager { method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int); method public boolean hasRegisteredDynamicPolicy(); + method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE}) public boolean isFullVolumeDevice(); } public static final class AudioRecord.MetricsConstants { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index f5f0b422a69d..86e2723a4dc8 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1628,6 +1628,18 @@ public class Activity extends ContextThemeWrapper } /** + * Clear the splash screen view if exist. + * @hide + */ + public void detachSplashScreenView() { + synchronized (this) { + if (mSplashScreenView != null) { + mSplashScreenView = null; + } + } + } + + /** * Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with * the attribute {@link android.R.attr#persistableMode} set to * <code>persistAcrossReboots</code>. diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index f905ec86aab7..3fedda36de2d 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -31,6 +31,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.UserIdInt; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; @@ -4048,7 +4049,8 @@ public class ActivityManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS}) public boolean switchUser(@NonNull UserHandle user) { if (user == null) { throw new IllegalArgumentException("UserHandle cannot be null."); @@ -4144,6 +4146,25 @@ public class ActivityManager { } } + /** + * Stops the given {@code userId}. + * + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + public boolean stopUser(@UserIdInt int userId, boolean force) { + if (userId == UserHandle.USER_SYSTEM) { + return false; + } + try { + return USER_OP_SUCCESS == getService().stopUser( + userId, force, /* callback= */ null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** {@hide} */ public static final int FLAG_OR_STOPPED = 1 << 0; /** {@hide} */ @@ -4811,6 +4832,21 @@ public class ActivityManager { } /** + * Blocks until all broadcast queues become idle. + * + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.DUMP) + public void waitForBroadcastIdle() { + try { + getService().waitForBroadcastIdle(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** * A subset of immutable pending intent information suitable for caching on the client side. * * @hide diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 837154fd6080..c1d8541311a2 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -292,7 +292,6 @@ public final class ActivityThread extends ClientTransactionHandler /** Use background GC policy and default JIT threshold. */ private static final int VM_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1; - private static final int REMOVE_SPLASH_SCREEN_VIEW_TIMEOUT = 5000; /** * Denotes an invalid sequence number corresponding to a process state change. */ @@ -1956,8 +1955,6 @@ public final class ActivityThread extends ClientTransactionHandler public static final int INSTRUMENT_WITHOUT_RESTART = 170; public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171; - public static final int REMOVE_SPLASH_SCREEN_VIEW = 172; - String codeToString(int code) { if (DEBUG_MESSAGES) { switch (code) { @@ -2006,8 +2003,6 @@ public final class ActivityThread extends ClientTransactionHandler case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART"; case FINISH_INSTRUMENTATION_WITHOUT_RESTART: return "FINISH_INSTRUMENTATION_WITHOUT_RESTART"; - case REMOVE_SPLASH_SCREEN_VIEW: - return "REMOVE_SPLASH_SCREEN_VIEW"; } } return Integer.toString(code); @@ -2204,9 +2199,6 @@ public final class ActivityThread extends ClientTransactionHandler case FINISH_INSTRUMENTATION_WITHOUT_RESTART: handleFinishInstrumentationWithoutRestart(); break; - case REMOVE_SPLASH_SCREEN_VIEW: - handleRemoveSplashScreenView((ActivityClientRecord) msg.obj); - break; } Object obj = msg.obj; if (obj instanceof SomeArgs) { @@ -4020,6 +4012,7 @@ public final class ActivityThread extends ClientTransactionHandler view.cacheRootWindow(r.window); view.makeSystemUIColorsTransparent(); r.activity.mSplashScreenView = view; + view.attachHostActivity(r.activity); view.requestLayout(); // Ensure splash screen view is shown before remove the splash screen window. final ViewRootImpl impl = decorView.getViewRootImpl(); @@ -4062,8 +4055,6 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void handOverSplashScreenView(@NonNull ActivityClientRecord r) { if (r.activity.mSplashScreenView != null) { - Message msg = mH.obtainMessage(H.REMOVE_SPLASH_SCREEN_VIEW, r); - mH.sendMessageDelayed(msg, REMOVE_SPLASH_SCREEN_VIEW_TIMEOUT); synchronized (this) { if (mSplashScreenGlobal != null) { mSplashScreenGlobal.dispatchOnExitAnimation(r.token, @@ -4074,16 +4065,6 @@ public final class ActivityThread extends ClientTransactionHandler } /** - * Force remove splash screen view. - */ - private void handleRemoveSplashScreenView(@NonNull ActivityClientRecord r) { - if (r.activity.mSplashScreenView != null) { - r.activity.mSplashScreenView.remove(); - r.activity.mSplashScreenView = null; - } - } - - /** * Cycle activity through onPause and onUserLeaveHint so that PIP is entered if supported, then * return to its previous state. This allows activities that rely on onUserLeaveHint instead of * onPictureInPictureRequested to enter picture-in-picture. diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 1c487e5fc6e1..27b19bcd31a1 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -57,7 +57,6 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserManager; import android.provider.DeviceConfig; -import android.provider.Settings; import android.util.ArrayMap; import android.util.ArraySet; import android.util.LongSparseArray; @@ -8114,8 +8113,8 @@ public class AppOpsManager { } else if (collectionMode == COLLECT_SYNC // Only collect app-ops when the proxy is trusted && (mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1, - myUid) == PackageManager.PERMISSION_GRANTED || isTrustedVoiceServiceProxy( - mContext, mContext.getOpPackageName(), op, mContext.getUserId()))) { + myUid) == PackageManager.PERMISSION_GRANTED || + Binder.getCallingUid() == proxiedUid)) { collectNotedOpSync(op, proxiedAttributionTag); } } @@ -8126,28 +8125,6 @@ public class AppOpsManager { } } - /** - * Checks if the voice recognition service is a trust proxy. - * - * @return {@code true} if the package is a trust voice recognition service proxy - * @hide - */ - public static boolean isTrustedVoiceServiceProxy(Context context, String packageName, - int code, int userId) { - // This is a workaround for R QPR, new API change is not allowed. We only allow the current - // voice recognizer is also the voice interactor to noteproxy op. - if (code != OP_RECORD_AUDIO) { - return false; - } - final String voiceRecognitionComponent = Settings.Secure.getStringForUser( - context.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE, userId); - - final String voiceRecognitionServicePackageName = - getComponentPackageNameFromString(voiceRecognitionComponent); - return (Objects.equals(packageName, voiceRecognitionServicePackageName)) - && isPackagePreInstalled(context, packageName, userId); - } - private static String getComponentPackageNameFromString(String from) { ComponentName componentName = from != null ? ComponentName.unflattenFromString(from) : null; return componentName != null ? componentName.getPackageName() : ""; @@ -8522,8 +8499,7 @@ public class AppOpsManager { // Only collect app-ops when the proxy is trusted && (mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1, Process.myUid()) == PackageManager.PERMISSION_GRANTED - || isTrustedVoiceServiceProxy(mContext, mContext.getOpPackageName(), opInt, - mContext.getUserId()))) { + || Binder.getCallingUid() == proxiedUid)) { collectNotedOpSync(opInt, proxiedAttributionTag); } } @@ -9172,7 +9148,7 @@ public class AppOpsManager { try { sFullLog = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, FULL_LOG, false); - } catch (SecurityException e) { + } catch (Exception e) { // This should not happen, but it may, in rare cases sFullLog = false; } diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index ef0dcabbe111..4c2433c04771 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -709,4 +709,7 @@ interface IActivityManager { ParceledListSlice queryIntentComponentsForIntentSender(in IIntentSender sender, int matchFlags); int getUidProcessCapabilities(int uid, in String callingPackage); + + /** Blocks until all broadcast queues become idle. */ + void waitForBroadcastIdle(); } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 930717b97555..56aa9a2b580c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2987,7 +2987,7 @@ public class DevicePolicyManager { /** * Checks if it's safe to run operations that can be affected by the given {@code reason}. * - * <p><b>Note:/b> notice that the operation safety state might change between the time this + * <p><b>Note:</b> notice that the operation safety state might change between the time this * method returns and the operation's method is called, so calls to the latter could still throw * a {@link UnsafeStateException} even when this method returns {@code true}. * diff --git a/core/java/android/app/time/Capabilities.java b/core/java/android/app/time/Capabilities.java new file mode 100644 index 000000000000..33db7211f3b4 --- /dev/null +++ b/core/java/android/app/time/Capabilities.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.time; + +import android.annotation.IntDef; +import android.annotation.SystemApi; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A capability is the ability for the user to configure something or perform an action. This + * information is exposed so that system apps like SettingsUI can be dynamic, rather than + * hard-coding knowledge of when configuration or actions are applicable / available to the user. + * + * <p>Capabilities have states that users cannot change directly. They may influence some + * capabilities indirectly by agreeing to certain device-wide behaviors such as location sharing, or + * by changing the configuration. See the {@code CAPABILITY_} constants for details. + * + * <p>Actions have associated methods, see the documentation for each action for details. + * + * <p>Note: Capabilities are independent of app permissions required to call the associated APIs. + * + * @hide + */ +@SystemApi +public final class Capabilities { + + /** @hide */ + @IntDef({ CAPABILITY_NOT_SUPPORTED, CAPABILITY_NOT_ALLOWED, CAPABILITY_NOT_APPLICABLE, + CAPABILITY_POSSESSED }) + @Retention(RetentionPolicy.SOURCE) + public @interface CapabilityState {} + + /** + * Indicates that a capability is not supported on this device, e.g. because of form factor or + * hardware. The associated UI should usually not be shown to the user. + */ + public static final int CAPABILITY_NOT_SUPPORTED = 10; + + /** + * Indicates that a capability is supported on this device, but not allowed for the user, e.g. + * if the capability relates to the ability to modify settings the user is not able to. + * This could be because of the user's type (e.g. maybe it applies to the primary user only) or + * device policy. Depending on the capability, this could mean the associated UI + * should be hidden, or displayed but disabled. + */ + public static final int CAPABILITY_NOT_ALLOWED = 20; + + /** + * Indicates that a capability is possessed but not currently applicable, e.g. if the + * capability relates to the ability to modify settings, the user has the ability to modify + * it, but it is currently rendered irrelevant by other settings or other device state (flags, + * resource config, etc.). The associated UI may be hidden, disabled, or left visible (but + * ineffective) depending on requirements. + */ + public static final int CAPABILITY_NOT_APPLICABLE = 30; + + /** Indicates that a capability is possessed by the user. */ + public static final int CAPABILITY_POSSESSED = 40; + + private Capabilities() {} + +} diff --git a/core/java/android/app/time/TimeCapabilities.aidl b/core/java/android/app/time/TimeCapabilities.aidl new file mode 100644 index 000000000000..f44b791e6ce7 --- /dev/null +++ b/core/java/android/app/time/TimeCapabilities.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.time; + +parcelable TimeCapabilities; diff --git a/core/java/android/app/time/TimeCapabilities.java b/core/java/android/app/time/TimeCapabilities.java new file mode 100644 index 000000000000..fff36c4a7096 --- /dev/null +++ b/core/java/android/app/time/TimeCapabilities.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.time; + +import android.annotation.NonNull; +import android.app.time.Capabilities.CapabilityState; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.UserHandle; + +import java.util.Objects; + +/** + * Time-relate capabilities for a user. + * + * <p>For configuration settings capabilities, the associated settings value can be found via + * {@link TimeManager#getTimeCapabilitiesAndConfig()} and may be changed using {@link + * TimeManager#updateTimeConfiguration(TimeConfiguration)} (if the user's capabilities + * allow). + * + * @hide + */ +public final class TimeCapabilities implements Parcelable { + + public static final @NonNull Creator<TimeCapabilities> CREATOR = + new Creator<TimeCapabilities>() { + public TimeCapabilities createFromParcel(Parcel in) { + return TimeCapabilities.createFromParcel(in); + } + + public TimeCapabilities[] newArray(int size) { + return new TimeCapabilities[size]; + } + }; + + + /** + * The user the capabilities are for. This is used for object equality and debugging but there + * is no accessor. + */ + @NonNull + private final UserHandle mUserHandle; + private final @CapabilityState int mConfigureAutoTimeDetectionEnabledCapability; + private final @CapabilityState int mSuggestTimeManuallyCapability; + + private TimeCapabilities(@NonNull Builder builder) { + this.mUserHandle = Objects.requireNonNull(builder.mUserHandle); + this.mConfigureAutoTimeDetectionEnabledCapability = + builder.mConfigureAutoDetectionEnabledCapability; + this.mSuggestTimeManuallyCapability = + builder.mSuggestTimeManuallyCapability; + } + + @NonNull + private static TimeCapabilities createFromParcel(Parcel in) { + UserHandle userHandle = UserHandle.readFromParcel(in); + return new TimeCapabilities.Builder(userHandle) + .setConfigureAutoTimeDetectionEnabledCapability(in.readInt()) + .setSuggestTimeManuallyCapability(in.readInt()) + .build(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + UserHandle.writeToParcel(mUserHandle, dest); + dest.writeInt(mConfigureAutoTimeDetectionEnabledCapability); + dest.writeInt(mSuggestTimeManuallyCapability); + } + + /** + * Returns the capability state associated with the user's ability to modify the automatic time + * detection setting. + */ + @CapabilityState + public int getConfigureAutoTimeDetectionEnabledCapability() { + return mConfigureAutoTimeDetectionEnabledCapability; + } + + /** + * Returns the capability state associated with the user's ability to manually set time on a + * device. + */ + @CapabilityState + public int getSuggestTimeManuallyCapability() { + return mSuggestTimeManuallyCapability; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TimeCapabilities that = (TimeCapabilities) o; + return mConfigureAutoTimeDetectionEnabledCapability + == that.mConfigureAutoTimeDetectionEnabledCapability + && mSuggestTimeManuallyCapability == that.mSuggestTimeManuallyCapability + && mUserHandle.equals(that.mUserHandle); + } + + @Override + public int hashCode() { + return Objects.hash(mUserHandle, mConfigureAutoTimeDetectionEnabledCapability, + mSuggestTimeManuallyCapability); + } + + @Override + public String toString() { + return "TimeCapabilities{" + + "mUserHandle=" + mUserHandle + + ", mConfigureAutoTimeDetectionEnabledCapability=" + + mConfigureAutoTimeDetectionEnabledCapability + + ", mSuggestTimeManuallyCapability=" + mSuggestTimeManuallyCapability + + '}'; + } + + /** + * A builder of {@link TimeCapabilities} objects. + * + * @hide + */ + public static class Builder { + @NonNull private final UserHandle mUserHandle; + private @CapabilityState int mConfigureAutoDetectionEnabledCapability; + private @CapabilityState int mSuggestTimeManuallyCapability; + + public Builder(@NonNull TimeCapabilities timeCapabilities) { + Objects.requireNonNull(timeCapabilities); + this.mUserHandle = timeCapabilities.mUserHandle; + this.mConfigureAutoDetectionEnabledCapability = + timeCapabilities.mConfigureAutoTimeDetectionEnabledCapability; + this.mSuggestTimeManuallyCapability = + timeCapabilities.mSuggestTimeManuallyCapability; + } + + public Builder(@NonNull UserHandle userHandle) { + this.mUserHandle = Objects.requireNonNull(userHandle); + } + + /** Sets the state for automatic time detection config. */ + public Builder setConfigureAutoTimeDetectionEnabledCapability( + @CapabilityState int setConfigureAutoTimeDetectionEnabledCapability) { + this.mConfigureAutoDetectionEnabledCapability = + setConfigureAutoTimeDetectionEnabledCapability; + return this; + } + + /** Sets the state for manual time change. */ + public Builder setSuggestTimeManuallyCapability( + @CapabilityState int suggestTimeManuallyCapability) { + this.mSuggestTimeManuallyCapability = suggestTimeManuallyCapability; + return this; + } + + /** Returns the {@link TimeCapabilities}. */ + public TimeCapabilities build() { + verifyCapabilitySet(mConfigureAutoDetectionEnabledCapability, + "configureAutoDetectionEnabledCapability"); + verifyCapabilitySet(mSuggestTimeManuallyCapability, "suggestTimeManuallyCapability"); + return new TimeCapabilities(this); + } + + private void verifyCapabilitySet(int value, String name) { + if (value == 0) { + throw new IllegalStateException(name + " was not set"); + } + } + } +} diff --git a/core/java/android/app/time/TimeCapabilitiesAndConfig.aidl b/core/java/android/app/time/TimeCapabilitiesAndConfig.aidl new file mode 100644 index 000000000000..183dcaf6e2e6 --- /dev/null +++ b/core/java/android/app/time/TimeCapabilitiesAndConfig.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.time; + +parcelable TimeCapabilitiesAndConfig;
\ No newline at end of file diff --git a/core/java/android/app/time/TimeCapabilitiesAndConfig.java b/core/java/android/app/time/TimeCapabilitiesAndConfig.java new file mode 100644 index 000000000000..4a1044760064 --- /dev/null +++ b/core/java/android/app/time/TimeCapabilitiesAndConfig.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.time; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * A pair containing a user's {@link TimeCapabilities} and {@link TimeConfiguration}. + * + * @hide + */ +public final class TimeCapabilitiesAndConfig implements Parcelable { + + public static final @NonNull Creator<TimeCapabilitiesAndConfig> CREATOR = + new Creator<TimeCapabilitiesAndConfig>() { + @Override + public TimeCapabilitiesAndConfig createFromParcel(Parcel source) { + return TimeCapabilitiesAndConfig.readFromParcel(source); + } + + @Override + public TimeCapabilitiesAndConfig[] newArray(int size) { + return new TimeCapabilitiesAndConfig[size]; + } + }; + + @NonNull + private final TimeCapabilities mTimeCapabilities; + + @NonNull + private final TimeConfiguration mTimeConfiguration; + + /** + * @hide + */ + public TimeCapabilitiesAndConfig(@NonNull TimeCapabilities timeCapabilities, + @NonNull TimeConfiguration timeConfiguration) { + mTimeCapabilities = Objects.requireNonNull(timeCapabilities); + mTimeConfiguration = Objects.requireNonNull(timeConfiguration); + } + + @NonNull + private static TimeCapabilitiesAndConfig readFromParcel(Parcel in) { + TimeCapabilities capabilities = in.readParcelable(null); + TimeConfiguration configuration = in.readParcelable(null); + return new TimeCapabilitiesAndConfig(capabilities, configuration); + } + + /** + * Returns the user's time behaviour capabilities. + * + * @hide + */ + @NonNull + public TimeCapabilities getTimeCapabilities() { + return mTimeCapabilities; + } + + /** + * Returns the user's time behaviour configuration. + * + * @hide + */ + @NonNull + public TimeConfiguration getTimeConfiguration() { + return mTimeConfiguration; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mTimeCapabilities, flags); + dest.writeParcelable(mTimeConfiguration, flags); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TimeCapabilitiesAndConfig that = (TimeCapabilitiesAndConfig) o; + return mTimeCapabilities.equals(that.mTimeCapabilities) + && mTimeConfiguration.equals(that.mTimeConfiguration); + } + + @Override + public int hashCode() { + return Objects.hash(mTimeCapabilities, mTimeConfiguration); + } + + @Override + public String toString() { + return "TimeCapabilitiesAndConfig{" + + "mTimeCapabilities=" + mTimeCapabilities + + ", mTimeConfiguration=" + mTimeConfiguration + + '}'; + } +} diff --git a/core/java/android/app/time/TimeConfiguration.aidl b/core/java/android/app/time/TimeConfiguration.aidl new file mode 100644 index 000000000000..eb5bfd6d2272 --- /dev/null +++ b/core/java/android/app/time/TimeConfiguration.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.time; + +parcelable TimeConfiguration; diff --git a/core/java/android/app/time/TimeConfiguration.java b/core/java/android/app/time/TimeConfiguration.java new file mode 100644 index 000000000000..70aede034d27 --- /dev/null +++ b/core/java/android/app/time/TimeConfiguration.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.time; + +import android.annotation.NonNull; +import android.annotation.StringDef; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * User visible settings that control the behavior of the time zone detector / manual time zone + * entry. + * + * @hide + */ +public final class TimeConfiguration implements Parcelable { + + public static final @NonNull Creator<TimeConfiguration> CREATOR = + new Creator<TimeConfiguration>() { + @Override + public TimeConfiguration createFromParcel(Parcel source) { + return TimeConfiguration.readFromParcel(source); + } + + @Override + public TimeConfiguration[] newArray(int size) { + return new TimeConfiguration[size]; + } + }; + + @StringDef(SETTING_AUTO_DETECTION_ENABLED) + @Retention(RetentionPolicy.SOURCE) + @interface Setting {} + + @Setting + private static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled"; + + @NonNull + private final Bundle mBundle; + + private TimeConfiguration(Builder builder) { + this.mBundle = builder.mBundle; + } + + /** + * Returns the value of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting. This + * controls whether a device will attempt to determine the time automatically using + * contextual information if the device supports auto detection. + */ + public boolean isAutoDetectionEnabled() { + return mBundle.getBoolean(SETTING_AUTO_DETECTION_ENABLED); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeBundle(mBundle); + } + + private static TimeConfiguration readFromParcel(Parcel in) { + return new TimeConfiguration.Builder() + .merge(in.readBundle()) + .build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TimeConfiguration that = (TimeConfiguration) o; + return mBundle.kindofEquals(that.mBundle); + } + + @Override + public int hashCode() { + return Objects.hash(mBundle); + } + + @Override + public String toString() { + return "TimeConfiguration{" + + "mBundle=" + mBundle + + '}'; + } + + /** + * A builder for {@link TimeConfiguration} objects. + * + * @hide + */ + public static final class Builder { + private final Bundle mBundle = new Bundle(); + + public Builder() {} + + public Builder(@NonNull TimeConfiguration configuration) { + mBundle.putAll(configuration.mBundle); + } + + /** Sets whether auto detection is enabled or not. */ + @NonNull + public Builder setAutoDetectionEnabled(boolean enabled) { + mBundle.putBoolean(SETTING_AUTO_DETECTION_ENABLED, enabled); + return this; + } + + Builder merge(@NonNull Bundle bundle) { + mBundle.putAll(bundle); + return this; + } + + /** Returns {@link TimeConfiguration} object. */ + @NonNull + public TimeConfiguration build() { + return new TimeConfiguration(this); + } + } +} diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java index 430960fb11a8..c8fa5c8f28e2 100644 --- a/core/java/android/app/time/TimeManager.java +++ b/core/java/android/app/time/TimeManager.java @@ -75,7 +75,7 @@ public final class TimeManager { @NonNull public TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig() { if (DEBUG) { - Log.d(TAG, "getTimeZoneCapabilities called"); + Log.d(TAG, "getTimeZoneCapabilitiesAndConfig called"); } try { return mITimeZoneDetectorService.getCapabilitiesAndConfig(); @@ -85,6 +85,44 @@ public final class TimeManager { } /** + * Returns the calling user's time capabilities and configuration. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) + @NonNull + public TimeCapabilitiesAndConfig getTimeCapabilitiesAndConfig() { + if (DEBUG) { + Log.d(TAG, "getTimeCapabilitiesAndConfig called"); + } + try { + return mITimeDetectorService.getCapabilitiesAndConfig(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Modifies the time detection configuration. + * + * @return {@code true} if all the configuration settings specified have been set to the + * new values, {@code false} if none have + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) + public boolean updateTimeConfiguration(@NonNull TimeConfiguration configuration) { + if (DEBUG) { + Log.d(TAG, "updateTimeConfiguration called: " + configuration); + } + try { + return mITimeDetectorService.updateConfiguration(configuration); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Modifies the time zone detection configuration. * * <p>Configuration settings vary in scope: some may be global (affect all users), others may be @@ -97,11 +135,11 @@ public final class TimeManager { * capabilities. * * <p>Attempts to modify configuration settings with capabilities that are {@link - * TimeZoneCapabilities#CAPABILITY_NOT_SUPPORTED} or {@link - * TimeZoneCapabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false} + * Capabilities#CAPABILITY_NOT_SUPPORTED} or {@link + * Capabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false} * will be returned. Modifying configuration settings with capabilities that are {@link - * TimeZoneCapabilities#CAPABILITY_NOT_APPLICABLE} or {@link - * TimeZoneCapabilities#CAPABILITY_POSSESSED} will succeed. See {@link + * Capabilities#CAPABILITY_NOT_APPLICABLE} or {@link + * Capabilities#CAPABILITY_POSSESSED} will succeed. See {@link * TimeZoneCapabilities} for further details. * * <p>If the supplied configuration only has some values set, then only the specified settings diff --git a/core/java/android/app/time/TimeZoneCapabilities.java b/core/java/android/app/time/TimeZoneCapabilities.java index a27be96e6e69..433b4200eece 100644 --- a/core/java/android/app/time/TimeZoneCapabilities.java +++ b/core/java/android/app/time/TimeZoneCapabilities.java @@ -16,77 +16,33 @@ package android.app.time; -import android.annotation.IntDef; +import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.app.time.Capabilities.CapabilityState; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TimeZoneDetector; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** - * Time zone-related capabilities for a user. A capability is the ability for the user to configure - * something or perform an action. This information is exposed so that system apps like SettingsUI - * can be dynamic, rather than hard-coding knowledge of when configuration or actions are applicable - * / available to the user. - * - * <p>Capabilities have states that users cannot change directly. They may influence some - * capabilities indirectly by agreeing to certain device-wide behaviors such as location sharing, or - * by changing the configuration. See the {@code CAPABILITY_} constants for details. - * - * <p>Actions have associated methods, see the documentation for each action for details. + * Time zone-related capabilities for a user. * * <p>For configuration settings capabilities, the associated settings value can be found via * {@link TimeManager#getTimeZoneCapabilitiesAndConfig()} and may be changed using {@link * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)} (if the user's capabilities * allow). * - * <p>Note: Capabilities are independent of app permissions required to call the associated APIs. - * * @hide */ @SystemApi public final class TimeZoneCapabilities implements Parcelable { - /** @hide */ - @IntDef({ CAPABILITY_NOT_SUPPORTED, CAPABILITY_NOT_ALLOWED, CAPABILITY_NOT_APPLICABLE, - CAPABILITY_POSSESSED }) - @Retention(RetentionPolicy.SOURCE) - public @interface CapabilityState {} - - /** - * Indicates that a capability is not supported on this device, e.g. because of form factor or - * hardware. The associated UI should usually not be shown to the user. - */ - public static final int CAPABILITY_NOT_SUPPORTED = 10; - - /** - * Indicates that a capability is supported on this device, but not allowed for the user, e.g. - * if the capability relates to the ability to modify settings the user is not able to. - * This could be because of the user's type (e.g. maybe it applies to the primary user only) or - * device policy. Depending on the capability, this could mean the associated UI - * should be hidden, or displayed but disabled. - */ - public static final int CAPABILITY_NOT_ALLOWED = 20; - - /** - * Indicates that a capability is possessed but not currently applicable, e.g. if the - * capability relates to the ability to modify settings, the user has the ability to modify - * it, but it is currently rendered irrelevant by other settings or other device state (flags, - * resource config, etc.). The associated UI may be hidden, disabled, or left visible (but - * ineffective) depending on requirements. - */ - public static final int CAPABILITY_NOT_APPLICABLE = 30; - - /** Indicates that a capability is possessed by the user. */ - public static final int CAPABILITY_POSSESSED = 40; - public static final @NonNull Creator<TimeZoneCapabilities> CREATOR = new Creator<TimeZoneCapabilities>() { public TimeZoneCapabilities createFromParcel(Parcel in) { @@ -159,7 +115,8 @@ public final class TimeZoneCapabilities implements Parcelable { * on a device via {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}. * * <p>The suggestion will be ignored in all cases unless the value is {@link - * #CAPABILITY_POSSESSED}. See also {@link TimeZoneConfiguration#isAutoDetectionEnabled()}. + * Capabilities#CAPABILITY_POSSESSED}. See also + * {@link TimeZoneConfiguration#isAutoDetectionEnabled()}. * * @hide */ diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java index f9a0c74312fc..a9ea76f77958 100644 --- a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java +++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java @@ -113,7 +113,7 @@ public final class TimeZoneCapabilitiesAndConfig implements Parcelable { @Override public String toString() { - return "TimeZoneDetectorCapabilitiesAndConfig{" + return "TimeZoneCapabilitiesAndConfig{" + "mCapabilities=" + mCapabilities + ", mConfiguration=" + mConfiguration + '}'; diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl index c4546be10601..9a6c33589123 100644 --- a/core/java/android/app/timedetector/ITimeDetectorService.aidl +++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl @@ -17,6 +17,8 @@ package android.app.timedetector; import android.app.time.ExternalTimeSuggestion; +import android.app.time.TimeCapabilitiesAndConfig; +import android.app.time.TimeConfiguration; import android.app.timedetector.GnssTimeSuggestion; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; @@ -36,6 +38,9 @@ import android.app.timedetector.TelephonyTimeSuggestion; * {@hide} */ interface ITimeDetectorService { + TimeCapabilitiesAndConfig getCapabilitiesAndConfig(); + boolean updateConfiguration(in TimeConfiguration timeConfiguration); + void suggestExternalTime( in ExternalTimeSuggestion timeSuggestion); void suggestGnssTime(in GnssTimeSuggestion timeSuggestion); boolean suggestManualTime(in ManualTimeSuggestion timeSuggestion); diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java index 098d8b6c6058..9f1132b605ef 100644 --- a/core/java/android/app/usage/NetworkStatsManager.java +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -24,6 +24,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.WorkerThread; import android.app.usage.NetworkStats.Bucket; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -201,6 +202,7 @@ public class NetworkStatsManager { * default network {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, * metered {@link NetworkStats.Bucket#METERED_ALL}, * and roaming {@link NetworkStats.Bucket#ROAMING_ALL}. + * This may take a long time, and apps should avoid calling this on their main thread. * * @param networkType As defined in {@link ConnectivityManager}, e.g. * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} @@ -219,6 +221,7 @@ public class NetworkStatsManager { * @return Bucket object or null if permissions are insufficient or error happened during * statistics collection. */ + @WorkerThread public Bucket querySummaryForDevice(int networkType, String subscriberId, long startTime, long endTime) throws SecurityException, RemoteException { NetworkTemplate template; @@ -240,6 +243,7 @@ public class NetworkStatsManager { * uid {@link NetworkStats.Bucket#UID_ALL}, tag {@link NetworkStats.Bucket#TAG_NONE}, * metered {@link NetworkStats.Bucket#METERED_ALL}, and roaming * {@link NetworkStats.Bucket#ROAMING_ALL}. + * This may take a long time, and apps should avoid calling this on their main thread. * * @param networkType As defined in {@link ConnectivityManager}, e.g. * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} @@ -258,6 +262,7 @@ public class NetworkStatsManager { * @return Bucket object or null if permissions are insufficient or error happened during * statistics collection. */ + @WorkerThread public Bucket querySummaryForUser(int networkType, String subscriberId, long startTime, long endTime) throws SecurityException, RemoteException { NetworkTemplate template; @@ -283,6 +288,7 @@ public class NetworkStatsManager { * means buckets' start and end timestamps are going to be the same as the 'startTime' and * 'endTime' parameters. State, uid, metered, and roaming are going to vary, and tag is going to * be the same. + * This may take a long time, and apps should avoid calling this on their main thread. * * @param networkType As defined in {@link ConnectivityManager}, e.g. * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} @@ -301,6 +307,7 @@ public class NetworkStatsManager { * @return Statistics object or null if permissions are insufficient or error happened during * statistics collection. */ + @WorkerThread public NetworkStats querySummary(int networkType, String subscriberId, long startTime, long endTime) throws SecurityException, RemoteException { NetworkTemplate template; @@ -326,9 +333,11 @@ public class NetworkStatsManager { /** * Query network usage statistics details for a given uid. + * This may take a long time, and apps should avoid calling this on their main thread. * * @see #queryDetailsForUidTagState(int, String, long, long, int, int, int) */ + @WorkerThread public NetworkStats queryDetailsForUid(int networkType, String subscriberId, long startTime, long endTime, int uid) throws SecurityException { return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid, @@ -344,9 +353,11 @@ public class NetworkStatsManager { /** * Query network usage statistics details for a given uid and tag. + * This may take a long time, and apps should avoid calling this on their main thread. * * @see #queryDetailsForUidTagState(int, String, long, long, int, int, int) */ + @WorkerThread public NetworkStats queryDetailsForUidTag(int networkType, String subscriberId, long startTime, long endTime, int uid, int tag) throws SecurityException { return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid, @@ -365,6 +376,7 @@ public class NetworkStatsManager { * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't * interpolate across partial buckets. Since bucket length is in the order of hours, this * method cannot be used to measure data usage on a fine grained time scale. + * This may take a long time, and apps should avoid calling this on their main thread. * * @param networkType As defined in {@link ConnectivityManager}, e.g. * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} @@ -387,6 +399,7 @@ public class NetworkStatsManager { * @return Statistics object or null if an error happened during statistics collection. * @throws SecurityException if permissions are insufficient to read network statistics. */ + @WorkerThread public NetworkStats queryDetailsForUidTagState(int networkType, String subscriberId, long startTime, long endTime, int uid, int tag, int state) throws SecurityException { NetworkTemplate template; @@ -425,6 +438,7 @@ public class NetworkStatsManager { * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't * interpolate across partial buckets. Since bucket length is in the order of hours, this * method cannot be used to measure data usage on a fine grained time scale. + * This may take a long time, and apps should avoid calling this on their main thread. * * @param networkType As defined in {@link ConnectivityManager}, e.g. * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} @@ -443,6 +457,7 @@ public class NetworkStatsManager { * @return Statistics object or null if permissions are insufficient or error happened during * statistics collection. */ + @WorkerThread public NetworkStats queryDetails(int networkType, String subscriberId, long startTime, long endTime) throws SecurityException, RemoteException { NetworkTemplate template; diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 8fd0de7dbb39..a6b4b47f0db2 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -520,9 +520,7 @@ public class AppWidgetHostView extends FrameLayout { return; } int layoutId = rvToApply.getLayoutId(); - // If our stale view has been prepared to match active, and the new - // layout matches, try recycling it - if (content == null && layoutId == mLayoutId) { + if (rvToApply.canRecycleView(mView)) { try { rvToApply.reapply(mContext, mView, mInteractionHandler, mCurrentSize, mColorResources); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index f8dd0e1452e5..64ca92fa8132 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3876,7 +3876,7 @@ public abstract class Context { * @see #getSystemService(String) * @hide */ - public static final String POWER_STATS_SERVICE = "power_stats"; + public static final String POWER_STATS_SERVICE = "powerstats"; /** * Use with {@link #getSystemService(String)} to retrieve a @@ -4219,7 +4219,8 @@ public abstract class Context { * @see #getSystemService(String) * @hide */ - @TestApi public static final String TEST_NETWORK_SERVICE = "test_network"; + @TestApi @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final String TEST_NETWORK_SERVICE = "test_network"; /** * Use with {@link #getSystemService(String)} to retrieve a {@link diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index adf9ff32c4ec..96b8fbe293f5 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -28,6 +28,7 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.AppGlobals; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; @@ -3771,6 +3772,7 @@ public class Intent implements Parcelable, Cloneable { * has just been stopped (which is no longer running). * @hide */ + @TestApi public static final String ACTION_USER_STOPPED = "android.intent.action.USER_STOPPED"; diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java index fe8e4d7b1bb2..6f07dd7a24e8 100644 --- a/core/java/android/content/pm/ResolveInfo.java +++ b/core/java/android/content/pm/ResolveInfo.java @@ -19,6 +19,7 @@ package android.content.pm; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; +import android.content.Intent; import android.content.IntentFilter; import android.graphics.drawable.Drawable; import android.os.Build; @@ -183,6 +184,17 @@ public class ResolveInfo implements Parcelable { @SystemApi public boolean handleAllWebDataURI; + /** + * Whether the resolved {@link IntentFilter} declares {@link Intent#CATEGORY_BROWSABLE} and is + * thus allowed to automatically resolve an {@link Intent} as it's assumed the action is safe + * for the user. + * + * Note that the above doesn't apply when this is the only result is returned in the candidate + * set, as the system will not prompt before opening the result. It only applies when there are + * multiple candidates. + */ + private final boolean mAutoResolutionAllowed; + /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public ComponentInfo getComponentInfo() { @@ -364,8 +376,26 @@ public class ResolveInfo implements Parcelable { && INTENT_FORWARDER_ACTIVITY.equals(activityInfo.targetActivity); } + /** + * @see #mAutoResolutionAllowed + * @hide + */ + public boolean isAutoResolutionAllowed() { + return mAutoResolutionAllowed; + } + public ResolveInfo() { targetUserId = UserHandle.USER_CURRENT; + + // It's safer to assume that an unaware caller that constructs a ResolveInfo doesn't + // accidentally mark a result as auto resolveable. + mAutoResolutionAllowed = false; + } + + /** @hide */ + public ResolveInfo(boolean autoResolutionAllowed) { + targetUserId = UserHandle.USER_CURRENT; + mAutoResolutionAllowed = autoResolutionAllowed; } public ResolveInfo(ResolveInfo orig) { @@ -386,6 +416,7 @@ public class ResolveInfo implements Parcelable { system = orig.system; targetUserId = orig.targetUserId; handleAllWebDataURI = orig.handleAllWebDataURI; + mAutoResolutionAllowed = orig.mAutoResolutionAllowed; isInstantAppAvailable = orig.isInstantAppAvailable; } @@ -450,6 +481,7 @@ public class ResolveInfo implements Parcelable { dest.writeInt(noResourceId ? 1 : 0); dest.writeInt(iconResourceId); dest.writeInt(handleAllWebDataURI ? 1 : 0); + dest.writeInt(mAutoResolutionAllowed ? 1 : 0); dest.writeInt(isInstantAppAvailable ? 1 : 0); } @@ -498,6 +530,7 @@ public class ResolveInfo implements Parcelable { noResourceId = source.readInt() != 0; iconResourceId = source.readInt(); handleAllWebDataURI = source.readInt() != 0; + mAutoResolutionAllowed = source.readInt() != 0; isInstantAppAvailable = source.readInt() != 0; } diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java index 7c335b1d26dd..62277ef671e3 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java @@ -43,9 +43,50 @@ import java.util.UUID; */ @SystemApi @DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true, - genEqualsHashCode = true) + genEqualsHashCode = true, genHiddenConstDefs = true) public final class DomainVerificationInfo implements Parcelable { + // Implementation note: the following states are OUTPUT only. Any value that is synonymous with + // a value in DomainVerificationState must be the EXACT same integer, so that state + // transformation does not have to occur when sending input into the system, assuming that the + // system only accepts those synonymous values. The public API values declared here are only + // used when exiting the system server to prepare this data object for consumption by the + // verification agent. These constants should only be referenced inside public API classes. + // The server must use DomainVerificationState. + + /** + * No response has been recorded by either the system or any verification agent. + */ + public static final int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE; + + /** + * The domain has been explicitly verified. + */ + public static final int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS; + + /** + * Indicates the host cannot be modified by the verification agent. + */ + public static final int STATE_UNMODIFIABLE = 2; + + /** + * Indicates the host can be modified by the verification agent and is not considered verified. + */ + public static final int STATE_MODIFIABLE_UNVERIFIED = 3; + + /** + * Indicates the host can be modified by the verification agent and is considered verified. + */ + public static final int STATE_MODIFIABLE_VERIFIED = 4; + + /** + * The first available custom response code. This and any greater integer, along with {@link + * #STATE_SUCCESS} are the only values settable by the verification agent. All custom values + * will be treated as if the domain is unverified. + */ + public static final int STATE_FIRST_VERIFIER_DEFINED = + DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED; + /** * A domain verification ID for use in later API calls. This represents the snapshot of the * domains for a package on device, and will be invalidated whenever the package changes. @@ -74,16 +115,12 @@ public final class DomainVerificationInfo implements Parcelable { /** * Map of host names to their current state. State is an integer, which defaults to {@link - * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain - * verification agent (the intended consumer of this API), which can be equal to {@link - * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link - * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. + * #STATE_NO_RESPONSE}. State can be modified by the domain verification agent (the intended + * consumer of this API), which can be equal to {@link #STATE_SUCCESS} when verified, or equal + * to or greater than {@link #STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. * <p> - * Any value non-inclusive between those 2 values are reserved for use by the system. The domain - * verification agent may be able to act on these reserved values, and this ability can be - * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that - * the agent attempt to verify all domains that it can modify the state of, even if it does not - * understand the meaning of those values. + * Hosts which cannot be edited will be assigned {@link #STATE_UNMODIFIABLE}. It is expected + * that the agent attempt to verify all domains that it can modify the state of. */ @NonNull private final Map<String, Integer> mHostToStateMap; @@ -112,6 +149,39 @@ public final class DomainVerificationInfo implements Parcelable { //@formatter:off + /** @hide */ + @android.annotation.IntDef(prefix = "STATE_", value = { + STATE_NO_RESPONSE, + STATE_SUCCESS, + STATE_UNMODIFIABLE, + STATE_MODIFIABLE_UNVERIFIED, + STATE_MODIFIABLE_VERIFIED, + STATE_FIRST_VERIFIER_DEFINED + }) + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface State {} + + /** @hide */ + @DataClass.Generated.Member + public static String stateToString(@State int value) { + switch (value) { + case STATE_NO_RESPONSE: + return "STATE_NO_RESPONSE"; + case STATE_SUCCESS: + return "STATE_SUCCESS"; + case STATE_UNMODIFIABLE: + return "STATE_UNMODIFIABLE"; + case STATE_MODIFIABLE_UNVERIFIED: + return "STATE_MODIFIABLE_UNVERIFIED"; + case STATE_MODIFIABLE_VERIFIED: + return "STATE_MODIFIABLE_VERIFIED"; + case STATE_FIRST_VERIFIER_DEFINED: + return "STATE_FIRST_VERIFIER_DEFINED"; + default: return Integer.toHexString(value); + } + } + /** * Creates a new DomainVerificationInfo. * @@ -134,16 +204,12 @@ public final class DomainVerificationInfo implements Parcelable { * The package name that this data corresponds to. * @param hostToStateMap * Map of host names to their current state. State is an integer, which defaults to {@link - * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain - * verification agent (the intended consumer of this API), which can be equal to {@link - * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link - * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. + * #STATE_NO_RESPONSE}. State can be modified by the domain verification agent (the intended + * consumer of this API), which can be equal to {@link #STATE_SUCCESS} when verified, or equal + * to or greater than {@link #STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. * <p> - * Any value non-inclusive between those 2 values are reserved for use by the system. The domain - * verification agent may be able to act on these reserved values, and this ability can be - * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that - * the agent attempt to verify all domains that it can modify the state of, even if it does not - * understand the meaning of those values. + * Hosts which cannot be edited will be assigned {@link #STATE_UNMODIFIABLE}. It is expected + * that the agent attempt to verify all domains that it can modify the state of. * @hide */ @DataClass.Generated.Member @@ -195,16 +261,12 @@ public final class DomainVerificationInfo implements Parcelable { /** * Map of host names to their current state. State is an integer, which defaults to {@link - * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain - * verification agent (the intended consumer of this API), which can be equal to {@link - * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link - * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. + * #STATE_NO_RESPONSE}. State can be modified by the domain verification agent (the intended + * consumer of this API), which can be equal to {@link #STATE_SUCCESS} when verified, or equal + * to or greater than {@link #STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. * <p> - * Any value non-inclusive between those 2 values are reserved for use by the system. The domain - * verification agent may be able to act on these reserved values, and this ability can be - * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that - * the agent attempt to verify all domains that it can modify the state of, even if it does not - * understand the meaning of those values. + * Hosts which cannot be edited will be assigned {@link #STATE_UNMODIFIABLE}. It is expected + * that the agent attempt to verify all domains that it can modify the state of. */ @DataClass.Generated.Member public @NonNull Map<String,Integer> getHostToStateMap() { @@ -320,10 +382,10 @@ public final class DomainVerificationInfo implements Parcelable { }; @DataClass.Generated( - time = 1614721812023L, + time = 1615317187669L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java", - inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)") + inputSignatures = "public static final int STATE_NO_RESPONSE\npublic static final int STATE_SUCCESS\npublic static final int STATE_UNMODIFIABLE\npublic static final int STATE_MODIFIABLE_UNVERIFIED\npublic static final int STATE_MODIFIABLE_VERIFIED\npublic static final int STATE_FIRST_VERIFIER_DEFINED\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java index f7c81bcffda3..55e19f2727bf 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java @@ -60,154 +60,97 @@ public final class DomainVerificationManager { "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST"; /** - * No response has been recorded by either the system or any verification agent. + * Default return code for when a method has succeeded. * * @hide */ @SystemApi - public static final int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE; + public static final int STATUS_OK = 0; /** - * The verification agent has explicitly verified the domain at some point. + * The provided domain set ID was invalid, probably due to the package being updated between + * the initial request that provided the ID and the method call that used it. This usually + * means the work being processed by the verification agent is outdated and a new request + * should be scheduled, which should already be in progress as part of the + * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast. * * @hide */ @SystemApi - public static final int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS; + public static final int ERROR_DOMAIN_SET_ID_INVALID = 1; /** - * The first available custom response code. This and any greater integer, along with {@link - * #STATE_SUCCESS} are the only values settable by the verification agent. All values will be - * treated as if the domain is unverified. + * The provided domain set ID was null. This is a developer error. * * @hide */ @SystemApi - public static final int STATE_FIRST_VERIFIER_DEFINED = - DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED; + public static final int ERROR_DOMAIN_SET_ID_NULL = 2; /** + * The provided set of domains was null or empty. This is a developer error. + * * @hide */ - @NonNull - public static String stateToDebugString(@DomainVerificationState.State int state) { - switch (state) { - case DomainVerificationState.STATE_NO_RESPONSE: - return "none"; - case DomainVerificationState.STATE_SUCCESS: - return "verified"; - case DomainVerificationState.STATE_APPROVED: - return "approved"; - case DomainVerificationState.STATE_DENIED: - return "denied"; - case DomainVerificationState.STATE_MIGRATED: - return "migrated"; - case DomainVerificationState.STATE_RESTORED: - return "restored"; - case DomainVerificationState.STATE_LEGACY_FAILURE: - return "legacy_failure"; - case DomainVerificationState.STATE_SYS_CONFIG: - return "system_configured"; - default: - return String.valueOf(state); - } - } + @SystemApi + public static final int ERROR_DOMAIN_SET_NULL_OR_EMPTY = 3; /** - * Checks if a state considers the corresponding domain to be successfully verified. The domain - * verification agent may use this to determine whether or not to re-verify a domain. + * The provided set of domains contains a domain not declared by the target package. This + * usually means the work being processed by the verification agent is outdated and a new + * request should be scheduled, which should already be in progress as part of the + * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast. * * @hide */ @SystemApi - public static boolean isStateVerified(@DomainVerificationState.State int state) { - switch (state) { - case DomainVerificationState.STATE_SUCCESS: - case DomainVerificationState.STATE_APPROVED: - case DomainVerificationState.STATE_MIGRATED: - case DomainVerificationState.STATE_RESTORED: - case DomainVerificationState.STATE_SYS_CONFIG: - return true; - case DomainVerificationState.STATE_NO_RESPONSE: - case DomainVerificationState.STATE_DENIED: - case DomainVerificationState.STATE_LEGACY_FAILURE: - default: - return false; - } - } + public static final int ERROR_UNKNOWN_DOMAIN = 4; /** - * Checks if a state is modifiable by the domain verification agent. This is useful as the - * platform may add new state codes in newer versions, and older verification agents can use - * this method to determine if a state can be changed without having to be aware of what the new - * state means. + * The system was unable to select the domain for approval. This indicates another application + * has been granted a higher approval, usually through domain verification, and the target + * package is unable to override it. * * @hide */ @SystemApi - public static boolean isStateModifiable(@DomainVerificationState.State int state) { - switch (state) { - case DomainVerificationState.STATE_NO_RESPONSE: - case DomainVerificationState.STATE_SUCCESS: - case DomainVerificationState.STATE_MIGRATED: - case DomainVerificationState.STATE_RESTORED: - case DomainVerificationState.STATE_LEGACY_FAILURE: - return true; - case DomainVerificationState.STATE_APPROVED: - case DomainVerificationState.STATE_DENIED: - case DomainVerificationState.STATE_SYS_CONFIG: - return false; - default: - return state >= DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED; - } - } + public static final int ERROR_UNABLE_TO_APPROVE = 5; /** - * For determine re-verify policy. This is hidden from the domain verification agent so that no - * behavior is made based on the result. + * The provided state code is incorrect. The domain verification agent is only allowed to + * assign {@link DomainVerificationInfo#STATE_SUCCESS} or error codes equal to or greater than + * {@link DomainVerificationInfo#STATE_FIRST_VERIFIER_DEFINED}. * * @hide */ - public static boolean isStateDefault(@DomainVerificationState.State int state) { - switch (state) { - case DomainVerificationState.STATE_NO_RESPONSE: - case DomainVerificationState.STATE_MIGRATED: - case DomainVerificationState.STATE_RESTORED: - return true; - case DomainVerificationState.STATE_SUCCESS: - case DomainVerificationState.STATE_APPROVED: - case DomainVerificationState.STATE_DENIED: - case DomainVerificationState.STATE_LEGACY_FAILURE: - case DomainVerificationState.STATE_SYS_CONFIG: - default: - return false; - } - } + @SystemApi + public static final int ERROR_INVALID_STATE_CODE = 6; /** + * Used to communicate through {@link ServiceSpecificException}. Should not be exposed as API. + * * @hide */ - public static final int ERROR_INVALID_DOMAIN_SET = 1; - /** - * @hide - */ - public static final int ERROR_NAME_NOT_FOUND = 2; + public static final int INTERNAL_ERROR_NAME_NOT_FOUND = 1; /** * @hide */ @IntDef(prefix = {"ERROR_"}, value = { - ERROR_INVALID_DOMAIN_SET, - ERROR_NAME_NOT_FOUND, + ERROR_DOMAIN_SET_ID_INVALID, + ERROR_DOMAIN_SET_ID_NULL, + ERROR_DOMAIN_SET_NULL_OR_EMPTY, + ERROR_UNKNOWN_DOMAIN, + ERROR_UNABLE_TO_APPROVE, + ERROR_INVALID_STATE_CODE }) - private @interface Error { + public @interface Error { } private final Context mContext; private final IDomainVerificationManager mDomainVerificationManager; - /** * System service to access the domain verification APIs. * <p> @@ -289,27 +232,24 @@ public final class DomainVerificationManager { * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}. * @param domains List of host names to change the state of. * @param state See {@link DomainVerificationInfo#getHostToStateMap()}. - * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are - * invalid. This usually means the work being processed by the - * verification agent is outdated and a new request should be - * scheduled, if one has not already been done as part of the - * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast. * @throws NameNotFoundException If the ID is known to be good, but the package is * unavailable. This may be because the package is installed on * a volume that is no longer mounted. This error is * unrecoverable until the package is available again, and * should not be re-tried except on a time scheduled basis. + * @return error code or {@link #STATUS_OK} if successful + * * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) - public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, - @DomainVerificationState.State int state) throws NameNotFoundException { + public int setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, + int state) throws NameNotFoundException { try { - mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(), + return mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(), new DomainSet(domains), state); } catch (Exception e) { - Exception converted = rethrow(e, domainSetId); + Exception converted = rethrow(e, null); if (converted instanceof NameNotFoundException) { throw (NameNotFoundException) converted; } else if (converted instanceof RuntimeException) { @@ -338,7 +278,7 @@ public final class DomainVerificationManager { mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName, allowed, mContext.getUserId()); } catch (Exception e) { - Exception converted = rethrow(e, packageName); + Exception converted = rethrow(e, null); if (converted instanceof NameNotFoundException) { throw (NameNotFoundException) converted; } else if (converted instanceof RuntimeException) { @@ -372,24 +312,24 @@ public final class DomainVerificationManager { * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}. * @param domains The domains to toggle the state of. * @param enabled Whether or not the app should automatically open the domains specified. - * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are - * invalid. * @throws NameNotFoundException If the ID is known to be good, but the package is * unavailable. This may be because the package is installed on * a volume that is no longer mounted. This error is * unrecoverable until the package is available again, and * should not be re-tried except on a time scheduled basis. + * @return error code or {@link #STATUS_OK} if successful + * * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) - public void setDomainVerificationUserSelection(@NonNull UUID domainSetId, + public int setDomainVerificationUserSelection(@NonNull UUID domainSetId, @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException { try { - mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(), - new DomainSet(domains), enabled, mContext.getUserId()); + return mDomainVerificationManager.setDomainVerificationUserSelection( + domainSetId.toString(), new DomainSet(domains), enabled, mContext.getUserId()); } catch (Exception e) { - Exception converted = rethrow(e, domainSetId); + Exception converted = rethrow(e, null); if (converted instanceof NameNotFoundException) { throw (NameNotFoundException) converted; } else if (converted instanceof RuntimeException) { @@ -447,123 +387,22 @@ public final class DomainVerificationManager { } } - private Exception rethrow(Exception exception, @Nullable UUID domainSetId) { - return rethrow(exception, domainSetId, null); - } - private Exception rethrow(Exception exception, @Nullable String packageName) { - return rethrow(exception, null, packageName); - } - - private Exception rethrow(Exception exception, @Nullable UUID domainSetId, - @Nullable String packageName) { if (exception instanceof ServiceSpecificException) { - int packedErrorCode = ((ServiceSpecificException) exception).errorCode; + int serviceSpecificErrorCode = ((ServiceSpecificException) exception).errorCode; if (packageName == null) { packageName = exception.getMessage(); } - @Error int managerErrorCode = packedErrorCode & 0xFFFF; - switch (managerErrorCode) { - case ERROR_INVALID_DOMAIN_SET: - int errorSpecificCode = packedErrorCode >> 16; - return new IllegalArgumentException(InvalidDomainSetException.buildMessage( - domainSetId, packageName, errorSpecificCode)); - case ERROR_NAME_NOT_FOUND: - return new NameNotFoundException(packageName); - default: - return exception; + if (serviceSpecificErrorCode == INTERNAL_ERROR_NAME_NOT_FOUND) { + return new NameNotFoundException(packageName); } + + return exception; } else if (exception instanceof RemoteException) { return ((RemoteException) exception).rethrowFromSystemServer(); } else { return exception; } } - - /** - * Thrown if a {@link DomainVerificationInfo#getIdentifier()}} or an associated set of domains - * provided by the caller is no longer valid. This may be recoverable, and the caller should - * re-query the package name associated with the ID using - * {@link #getDomainVerificationInfo(String)} - * in order to check. If that also fails, then the package is no longer known to the device and - * thus all pending work for it should be dropped. - * - * @hide - */ - public static class InvalidDomainSetException extends IllegalArgumentException { - - public static final int REASON_ID_NULL = 1; - public static final int REASON_ID_INVALID = 2; - public static final int REASON_SET_NULL_OR_EMPTY = 3; - public static final int REASON_UNKNOWN_DOMAIN = 4; - public static final int REASON_UNABLE_TO_APPROVE = 5; - - /** - * @hide - */ - @IntDef({ - REASON_ID_NULL, - REASON_ID_INVALID, - REASON_SET_NULL_OR_EMPTY, - REASON_UNKNOWN_DOMAIN, - REASON_UNABLE_TO_APPROVE - }) - public @interface Reason { - } - - public static String buildMessage(@Nullable UUID domainSetId, @Nullable String packageName, - @Reason int reason) { - switch (reason) { - case REASON_ID_NULL: - return "Domain set ID cannot be null"; - case REASON_ID_INVALID: - return "Domain set ID " + domainSetId + " has been invalidated"; - case REASON_SET_NULL_OR_EMPTY: - return "Domain set cannot be null or empty"; - case REASON_UNKNOWN_DOMAIN: - return "Domain set contains value that was not declared by the target package " - + packageName; - case REASON_UNABLE_TO_APPROVE: - return "Domain set contains value that was owned by another package"; - default: - return "Unknown failure"; - } - } - - @Reason - private final int mReason; - - @Nullable - private final UUID mDomainSetId; - - @Nullable - private final String mPackageName; - - /** - * @hide - */ - public InvalidDomainSetException(@Nullable UUID domainSetId, @Nullable String packageName, - @Reason int reason) { - super(buildMessage(domainSetId, packageName, reason)); - mDomainSetId = domainSetId; - mPackageName = packageName; - mReason = reason; - } - - @Nullable - public UUID getDomainSetId() { - return mDomainSetId; - } - - @Nullable - public String getPackageName() { - return mPackageName; - } - - @Reason - public int getReason() { - return mReason; - } - } } diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationState.java b/core/java/android/content/pm/verify/domain/DomainVerificationState.java index 17593ef2aeb1..8e28042bf581 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationState.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationState.java @@ -17,15 +17,13 @@ package android.content.pm.verify.domain; import android.annotation.IntDef; +import android.annotation.NonNull; /** * @hide */ public interface DomainVerificationState { - /** - * @hide - */ @IntDef({ STATE_NO_RESPONSE, STATE_SUCCESS, @@ -42,12 +40,12 @@ public interface DomainVerificationState { // TODO(b/159952358): Document all the places that states need to be updated when one is added /** - * @see DomainVerificationManager#STATE_NO_RESPONSE + * @see DomainVerificationInfo#STATE_NO_RESPONSE */ int STATE_NO_RESPONSE = 0; /** - * @see DomainVerificationManager#STATE_SUCCESS + * @see DomainVerificationInfo#STATE_SUCCESS */ int STATE_SUCCESS = 1; @@ -94,7 +92,132 @@ public interface DomainVerificationState { int STATE_SYS_CONFIG = 7; /** - * @see DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED + * @see DomainVerificationInfo#STATE_FIRST_VERIFIER_DEFINED */ int STATE_FIRST_VERIFIER_DEFINED = 0b10000000000; + + @NonNull + static String stateToDebugString(@DomainVerificationState.State int state) { + switch (state) { + case DomainVerificationState.STATE_NO_RESPONSE: + return "none"; + case DomainVerificationState.STATE_SUCCESS: + return "verified"; + case DomainVerificationState.STATE_APPROVED: + return "approved"; + case DomainVerificationState.STATE_DENIED: + return "denied"; + case DomainVerificationState.STATE_MIGRATED: + return "migrated"; + case DomainVerificationState.STATE_RESTORED: + return "restored"; + case DomainVerificationState.STATE_LEGACY_FAILURE: + return "legacy_failure"; + case DomainVerificationState.STATE_SYS_CONFIG: + return "system_configured"; + default: + return String.valueOf(state); + } + } + + /** + * For determining re-verify policy. This is hidden from the domain verification agent so that + * no behavior is made based on the result. + */ + static boolean isDefault(@State int state) { + switch (state) { + case STATE_NO_RESPONSE: + case STATE_MIGRATED: + case STATE_RESTORED: + return true; + case STATE_SUCCESS: + case STATE_APPROVED: + case STATE_DENIED: + case STATE_LEGACY_FAILURE: + case STATE_SYS_CONFIG: + default: + return false; + } + } + + /** + * Checks if a state considers the corresponding domain to be successfully verified. The domain + * verification agent may use this to determine whether or not to re-verify a domain. + */ + static boolean isVerified(@DomainVerificationState.State int state) { + switch (state) { + case DomainVerificationState.STATE_SUCCESS: + case DomainVerificationState.STATE_APPROVED: + case DomainVerificationState.STATE_MIGRATED: + case DomainVerificationState.STATE_RESTORED: + case DomainVerificationState.STATE_SYS_CONFIG: + return true; + case DomainVerificationState.STATE_NO_RESPONSE: + case DomainVerificationState.STATE_DENIED: + case DomainVerificationState.STATE_LEGACY_FAILURE: + default: + return false; + } + } + + /** + * Checks if a state is modifiable by the domain verification agent. This is useful as the + * platform may add new state codes in newer versions, and older verification agents can use + * this method to determine if a state can be changed without having to be aware of what the new + * state means. + */ + static boolean isModifiable(@DomainVerificationState.State int state) { + switch (state) { + case DomainVerificationState.STATE_NO_RESPONSE: + case DomainVerificationState.STATE_SUCCESS: + case DomainVerificationState.STATE_MIGRATED: + case DomainVerificationState.STATE_RESTORED: + case DomainVerificationState.STATE_LEGACY_FAILURE: + return true; + case DomainVerificationState.STATE_APPROVED: + case DomainVerificationState.STATE_DENIED: + case DomainVerificationState.STATE_SYS_CONFIG: + return false; + default: + return state >= DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED; + } + } + + /** + * Whether the state is migrated when updating a package. Generally this is only for states + * that maintain verification state or were set by an explicit user or developer action. + */ + static boolean shouldMigrate(@State int state) { + switch (state) { + case STATE_SUCCESS: + case STATE_MIGRATED: + case STATE_RESTORED: + case STATE_APPROVED: + case STATE_DENIED: + return true; + case STATE_NO_RESPONSE: + case STATE_LEGACY_FAILURE: + case STATE_SYS_CONFIG: + case STATE_FIRST_VERIFIER_DEFINED: + default: + return false; + } + } + + @DomainVerificationInfo.State + static int convertToInfoState(@State int internalState) { + if (internalState >= STATE_FIRST_VERIFIER_DEFINED) { + return internalState; + } else if (internalState == STATE_NO_RESPONSE) { + return DomainVerificationInfo.STATE_NO_RESPONSE; + } else if (internalState == STATE_SUCCESS) { + return DomainVerificationInfo.STATE_SUCCESS; + } else if (!isModifiable(internalState)) { + return DomainVerificationInfo.STATE_UNMODIFIABLE; + } else if (isVerified(internalState)) { + return DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED; + } else { + return DomainVerificationInfo.STATE_MODIFIABLE_UNVERIFIED; + } + } } diff --git a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl index 332b92544581..53205f3ea470 100644 --- a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl +++ b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl @@ -40,10 +40,10 @@ interface IDomainVerificationManager { @nullable List<DomainOwner> getOwnersForDomain(String domain, int userId); - void setDomainVerificationStatus(String domainSetId, in DomainSet domains, int state); + int setDomainVerificationStatus(String domainSetId, in DomainSet domains, int state); void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed, int userId); - void setDomainVerificationUserSelection(String domainSetId, in DomainSet domains, + int setDomainVerificationUserSelection(String domainSetId, in DomainSet domains, boolean enabled, int userId); } diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 1fdce5e773b1..acfad1354ccf 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -29,6 +29,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.content.Context; +import android.os.IBinder; import android.os.RemoteException; import android.security.keystore.KeyProperties; import android.util.Slog; @@ -410,6 +411,36 @@ public class BiometricManager { } /** + * Requests all other biometric sensors to resetLockout. Note that this is a "time bound" + * See the {@link android.hardware.biometrics.fingerprint.ISession#resetLockout(int, + * HardwareAuthToken)} and {@link android.hardware.biometrics.face.ISession#resetLockout(int, + * HardwareAuthToken)} documentation for complete details. + * + * @param token A binder from the caller, for the service to linkToDeath + * @param opPackageName Caller's package name + * @param fromSensorId The originating sensor that just authenticated. Note that this MUST + * be a sensor that meets {@link Authenticators#BIOMETRIC_STRONG} strength. + * The strength will also be enforced on the BiometricService side. + * @param userId The user that authentication succeeded for, and also the user that resetLockout + * should be applied to. + * @param hardwareAuthToken A valid HAT generated upon successful biometric authentication. Note + * that it is not necessary for the HAT to contain a challenge. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void resetLockoutTimeBound(IBinder token, String opPackageName, int fromSensorId, + int userId, byte[] hardwareAuthToken) { + if (mService != null) { + try { + mService.resetLockoutTimeBound(token, opPackageName, fromSensorId, userId, + hardwareAuthToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** * Provides a localized string that may be used as the label for a button that invokes * {@link BiometricPrompt}. * diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl index 1472bb940be5..86df0994a222 100644 --- a/core/java/android/hardware/biometrics/IAuthService.aidl +++ b/core/java/android/hardware/biometrics/IAuthService.aidl @@ -69,6 +69,10 @@ interface IAuthService { // land as SIDs, and are used during key generation. long[] getAuthenticatorIds(); + // See documentation in BiometricManager. + void resetLockoutTimeBound(IBinder token, String opPackageName, int fromSensorId, int userId, + in byte[] hardwareAuthToken); + // Provides a localized string that may be used as the label for a button that invokes // BiometricPrompt. CharSequence getButtonLabel(int userId, String opPackageName, int authenticators); diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl index 7639c5dd4d16..059bf2622b00 100644 --- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl +++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl @@ -70,4 +70,8 @@ interface IBiometricAuthenticator { // Gets the authenticator ID representing the current set of enrolled templates long getAuthenticatorId(int callingUserId); + + // Requests the sensor to reset its lockout state + void resetLockout(IBinder token, String opPackageName, int userId, + in byte[] hardwareAuthToken); } diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index 6d8bf0fb5543..64b51183a170 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -74,6 +74,10 @@ interface IBiometricService { // land as SIDs, and are used during key generation. long[] getAuthenticatorIds(int callingUserId); + // See documentation in BiometricManager. + void resetLockoutTimeBound(IBinder token, String opPackageName, int fromSensorId, int userId, + in byte[] hardwareAuthToken); + int getCurrentStrength(int sensorId); // Returns a bit field of the modality (or modalities) that are will be used for authentication. diff --git a/core/java/android/hardware/camera2/MultiResolutionImageReader.java b/core/java/android/hardware/camera2/MultiResolutionImageReader.java index c592f19bc45c..bb3d91dbc65c 100644 --- a/core/java/android/hardware/camera2/MultiResolutionImageReader.java +++ b/core/java/android/hardware/camera2/MultiResolutionImageReader.java @@ -131,18 +131,10 @@ public class MultiResolutionImageReader implements AutoCloseable { * @see * android.hardware.camera2.params.MultiResolutionStreamConfigurationMap */ - public static @NonNull MultiResolutionImageReader newInstance( + public MultiResolutionImageReader( @NonNull Collection<MultiResolutionStreamInfo> streams, @Format int format, @IntRange(from = 1) int maxImages) { - return new MultiResolutionImageReader(streams, format, maxImages); - } - - /** - * @hide - */ - protected MultiResolutionImageReader(Collection<MultiResolutionStreamInfo> streams, - int format, int maxImages) { mFormat = format; mMaxImages = maxImages; diff --git a/core/java/android/hardware/camera2/params/MultiResolutionStreamInfo.java b/core/java/android/hardware/camera2/params/MultiResolutionStreamInfo.java index aa1d1d4aaa18..e2e61ad4d31a 100644 --- a/core/java/android/hardware/camera2/params/MultiResolutionStreamInfo.java +++ b/core/java/android/hardware/camera2/params/MultiResolutionStreamInfo.java @@ -17,6 +17,7 @@ package android.hardware.camera2.params; import android.annotation.NonNull; +import android.annotation.IntRange; import java.util.Objects; @@ -50,9 +51,22 @@ public class MultiResolutionStreamInfo { * MultiResolutionStreamConfigurationMap#getOutputInfo} or {@link * MultiResolutionStreamConfigurationMap#getInputInfo} to obtain them for a particular format * instead.</p> + * + * @param streamWidth The width in pixels of the camera stream + * @param streamHeight The height in pixels of the camera stream + * @param physicalCameraId The physical camera Id the camera stream is associated with + * @throws IllegalArgumentException if the streamWidth or streamHeight is invalid (either zero + * or negative). */ - public MultiResolutionStreamInfo(int streamWidth, int streamHeight, + public MultiResolutionStreamInfo(@IntRange(from = 1) int streamWidth, + @IntRange(from = 1) int streamHeight, @NonNull String physicalCameraId) { + if (streamWidth <= 0) { + throw new IllegalArgumentException("Invalid stream width " + streamWidth); + } + if (streamHeight <= 0) { + throw new IllegalArgumentException("Invalid stream height " + streamHeight); + } mStreamWidth = streamWidth; mStreamHeight = streamHeight; mPhysicalCameraId = physicalCameraId; @@ -61,14 +75,14 @@ public class MultiResolutionStreamInfo { /** * The width of this particular image buffer stream in pixels. */ - public int getWidth() { + public @IntRange(from = 1) int getWidth() { return mStreamWidth; } /** * The height of this particular image buffer stream in pixels. */ - public int getHeight() { + public @IntRange(from = 1) int getHeight() { return mStreamHeight; } diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl index dfb1e996c55a..00c691379187 100644 --- a/core/java/android/net/INetworkPolicyListener.aidl +++ b/core/java/android/net/INetworkPolicyListener.aidl @@ -25,4 +25,5 @@ oneway interface INetworkPolicyListener { void onUidPoliciesChanged(int uid, int uidPolicies); void onSubscriptionOverride(int subId, int overrideMask, int overrideValue, in int[] networkTypes); void onSubscriptionPlansChanged(int subId, in SubscriptionPlan[] plans); + void onBlockedReasonChanged(int uid, int oldBlockedReason, int newBlockedReason); } diff --git a/packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl b/core/java/android/net/IOnCompleteListener.aidl index 7979afc54f90..4bb89f6c89e4 100644 --- a/packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl +++ b/core/java/android/net/IOnCompleteListener.aidl @@ -18,6 +18,6 @@ package android.net; /** @hide */ -oneway interface IOnSetOemNetworkPreferenceListener { +oneway interface IOnCompleteListener { void onComplete(); } diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 664120698971..0070796db410 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -23,6 +23,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.ActivityManager; @@ -44,6 +45,8 @@ import android.util.DebugUtils; import android.util.Pair; import android.util.Range; +import com.android.internal.util.function.pooled.PooledLambda; + import com.google.android.collect.Sets; import java.lang.annotation.Retention; @@ -53,6 +56,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; /** * Manager for creating and modifying network policy rules. @@ -60,6 +64,7 @@ import java.util.concurrent.ConcurrentHashMap; * @hide */ @TestApi +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @SystemService(Context.NETWORK_POLICY_SERVICE) public class NetworkPolicyManager { @@ -198,12 +203,157 @@ public class NetworkPolicyManager { }) public @interface SubscriptionOverrideMask {} + /** + * Flag to indicate that an app is not subject to any restrictions that could result in its + * network access blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_NONE = 0; + + /** + * Flag to indicate that an app is subject to Battery saver restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_BATTERY_SAVER = 1 << 0; + + /** + * Flag to indicate that an app is subject to Doze restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_DOZE = 1 << 1; + + /** + * Flag to indicate that an app is subject to App Standby restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_APP_STANDBY = 1 << 2; + + /** + * Flag to indicate that an app is subject to Restricted mode restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_RESTRICTED_MODE = 1 << 3; + + /** + * Flag to indicate that an app is subject to Data saver restrictions that would + * result in its metered network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_METERED_REASON_DATA_SAVER = 1 << 16; + + /** + * Flag to indicate that an app is subject to user restrictions that would + * result in its metered network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 1 << 17; + + /** + * Flag to indicate that an app is subject to Device admin restrictions that would + * result in its metered network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 1 << 18; + + /** @hide */ + public static final int BLOCKED_METERED_REASON_MASK = 0xffff0000; + + /** + * Flag to indicate that app is not exempt from any network restrictions. + * + * @hide + */ + public static final int ALLOWED_REASON_NONE = 0; + /** + * Flag to indicate that app is exempt from certain network restrictions because of it being a + * system component. + * + * @hide + */ + public static final int ALLOWED_REASON_SYSTEM = 1 << 0; + /** + * Flag to indicate that app is exempt from certain network restrictions because of it being + * in the foreground. + * + * @hide + */ + public static final int ALLOWED_REASON_FOREGROUND = 1 << 1; + /** + * Flag to indicate that app is exempt from certain network restrictions because of it being + * in the {@code allow-in-power-save} list. + * + * @hide + */ + public static final int ALLOWED_REASON_POWER_SAVE_ALLOWLIST = 1 << 2; + /** + * Flag to indicate that app is exempt from certain network restrictions because of it being + * in the {@code allow-in-power-save-except-idle} list. + * + * @hide + */ + public static final int ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST = 1 << 3; + /** + * Flag to indicate that app is exempt from certain network restrictions because of it holding + * certain privileged permissions. + * + * @hide + */ + public static final int ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS = 1 << 4; + /** + * Flag to indicate that app is exempt from certain metered network restrictions because user + * explicitly exempted it. + * + * @hide + */ + public static final int ALLOWED_METERED_REASON_USER_EXEMPTED = 1 << 16; + + /** @hide */ + public static final int ALLOWED_METERED_REASON_MASK = 0xffff0000; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = {"BLOCKED_"}, value = { + BLOCKED_REASON_NONE, + BLOCKED_REASON_BATTERY_SAVER, + BLOCKED_REASON_DOZE, + BLOCKED_REASON_APP_STANDBY, + BLOCKED_REASON_RESTRICTED_MODE, + BLOCKED_METERED_REASON_DATA_SAVER, + BLOCKED_METERED_REASON_USER_RESTRICTED, + BLOCKED_METERED_REASON_ADMIN_DISABLED, + }) + public @interface BlockedReason {} + private final Context mContext; @UnsupportedAppUsage private INetworkPolicyManager mService; private final Map<SubscriptionCallback, SubscriptionCallbackProxy> - mCallbackMap = new ConcurrentHashMap<>(); + mSubscriptionCallbackMap = new ConcurrentHashMap<>(); + private final Map<NetworkPolicyCallback, NetworkPolicyCallbackProxy> + mNetworkPolicyCallbackMap = new ConcurrentHashMap<>(); /** @hide */ public NetworkPolicyManager(Context context, INetworkPolicyManager service) { @@ -318,7 +468,7 @@ public class NetworkPolicyManager { } final SubscriptionCallbackProxy callbackProxy = new SubscriptionCallbackProxy(callback); - if (null != mCallbackMap.putIfAbsent(callback, callbackProxy)) { + if (null != mSubscriptionCallbackMap.putIfAbsent(callback, callbackProxy)) { throw new IllegalArgumentException("Callback is already registered."); } registerListener(callbackProxy); @@ -331,7 +481,7 @@ public class NetworkPolicyManager { throw new NullPointerException("Callback cannot be null."); } - final SubscriptionCallbackProxy callbackProxy = mCallbackMap.remove(callback); + final SubscriptionCallbackProxy callbackProxy = mSubscriptionCallbackMap.remove(callback); if (callbackProxy == null) return; unregisterListener(callbackProxy); @@ -689,6 +839,142 @@ public class NetworkPolicyManager { return WifiInfo.sanitizeSsid(ssid); } + /** + * Returns whether network access of an UID is blocked or not based on {@code blockedReasons} + * corresponding to it. + * + * {@code blockedReasons} would be a bitwise {@code OR} combination of the + * {@code BLOCKED_REASON_*} and/or {@code BLOCKED_METERED_REASON_*} constants. + * + * @param blockedReasons Value indicating the reasons for why the network access of an UID is + * blocked. If the value is equal to {@link #BLOCKED_REASON_NONE}, then + * it indicates that an app's network access is not blocked. + * @param meteredNetwork Value indicating whether the network is metered or not. + * @return Whether network access is blocked or not. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static boolean isUidBlocked(@BlockedReason int blockedReasons, boolean meteredNetwork) { + if (blockedReasons == BLOCKED_REASON_NONE) { + return false; + } + final int blockedOnAllNetworksReason = (blockedReasons & ~BLOCKED_METERED_REASON_MASK); + if (blockedOnAllNetworksReason != BLOCKED_REASON_NONE) { + return true; + } + if (meteredNetwork) { + return blockedReasons != BLOCKED_REASON_NONE; + } + return false; + } + + /** + * Returns the {@code string} representation of {@code blockedReasons} argument. + * + * @param blockedReasons Value indicating the reasons for why the network access of an UID is + * blocked. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @NonNull + public static String blockedReasonsToString(@BlockedReason int blockedReasons) { + return DebugUtils.flagsToString(NetworkPolicyManager.class, "BLOCKED_", blockedReasons); + } + + /** + * Register a {@link NetworkPolicyCallback} to listen for changes to network blocked status + * of apps. + * + * Note that when a caller tries to register a new callback, it might replace a previously + * registered callback if it is considered equal to the new one, based on the + * {@link Object#equals(Object)} check. + * + * @param executor The {@link Executor} to run the callback on. + * @param callback The {@link NetworkPolicyCallback} to be registered. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) + public void registerNetworkPolicyCallback(@Nullable Executor executor, + @NonNull NetworkPolicyCallback callback) { + if (callback == null) { + throw new NullPointerException("Callback cannot be null."); + } + + final NetworkPolicyCallbackProxy callbackProxy = new NetworkPolicyCallbackProxy( + executor, callback); + registerListener(callbackProxy); + mNetworkPolicyCallbackMap.put(callback, callbackProxy); + } + + /** + * Unregister a previously registered {@link NetworkPolicyCallback}. + * + * @param callback The {@link NetworkPolicyCallback} to be unregistered. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) + public void unregisterNetworkPolicyCallback(@NonNull NetworkPolicyCallback callback) { + if (callback == null) { + throw new NullPointerException("Callback cannot be null."); + } + + final NetworkPolicyCallbackProxy callbackProxy = mNetworkPolicyCallbackMap.remove(callback); + if (callbackProxy == null) return; + unregisterListener(callbackProxy); + } + + /** + * Interface for the callback to listen for changes to network blocked status of apps. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public interface NetworkPolicyCallback { + /** + * Called when the reason for why the network access of an UID is blocked changes. + * + * @param uid The UID for which the blocked status changed. + * @param blockedReasons Value indicating the reasons for why the network access of an + * UID is blocked. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + default void onUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {} + } + + /** @hide */ + public static class NetworkPolicyCallbackProxy extends Listener { + private final Executor mExecutor; + private final NetworkPolicyCallback mCallback; + + NetworkPolicyCallbackProxy(@Nullable Executor executor, + @NonNull NetworkPolicyCallback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onBlockedReasonChanged(int uid, @BlockedReason int oldBlockedReasons, + @BlockedReason int newBlockedReasons) { + if (oldBlockedReasons != newBlockedReasons) { + dispatchOnUidBlockedReasonChanged(mExecutor, mCallback, uid, newBlockedReasons); + } + } + } + + private static void dispatchOnUidBlockedReasonChanged(@Nullable Executor executor, + @NonNull NetworkPolicyCallback callback, int uid, @BlockedReason int blockedReasons) { + if (executor == null) { + callback.onUidBlockedReasonChanged(uid, blockedReasons); + } else { + executor.execute(PooledLambda.obtainRunnable( + NetworkPolicyCallback::onUidBlockedReasonChanged, + callback, uid, blockedReasons).recycleOnUse()); + } + } + /** @hide */ public static class SubscriptionCallback { /** @@ -743,5 +1029,7 @@ public class NetworkPolicyManager { @Override public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue, int[] networkTypes) { } @Override public void onSubscriptionPlansChanged(int subId, SubscriptionPlan[] plans) { } + @Override public void onBlockedReasonChanged(int uid, + int oldBlockedReasons, int newBlockedReasons) { } } } diff --git a/core/res/res/values/vendor_allowed_personal_apps_org_owned_device.xml b/core/java/android/net/NetworkScore.aidl index 0435c301a2da..af12dcf7f17a 100644 --- a/core/res/res/values/vendor_allowed_personal_apps_org_owned_device.xml +++ b/core/java/android/net/NetworkScore.aidl @@ -1,7 +1,5 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- /** - * Copyright (C) 2020 The Android Open Source Project + * Copyright (c) 2021, 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. @@ -15,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ ---> -<resources> - <!-- A list of apps to be allowed in the personal profile of an organization-owned device. --> - <string-array translatable="false" name="vendor_allowed_personal_apps_org_owned_device"> - </string-array> -</resources> + +package android.net; + +parcelable NetworkScore; + diff --git a/core/java/android/net/NetworkScore.java b/core/java/android/net/NetworkScore.java new file mode 100644 index 000000000000..f47801002296 --- /dev/null +++ b/core/java/android/net/NetworkScore.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Object representing the quality of a network as perceived by the user. + * + * A NetworkScore object represents the characteristics of a network that affects how good the + * network is considered for a particular use. + * @hide + */ +// TODO : @SystemApi when the implementation is complete +public final class NetworkScore implements Parcelable { + // This will be removed soon. Do *NOT* depend on it for any new code that is not part of + // a migration. + private final int mLegacyInt; + + /** @hide */ + NetworkScore(final int legacyInt) { + this.mLegacyInt = legacyInt; + } + + private NetworkScore(@NonNull final Parcel in) { + mLegacyInt = in.readInt(); + } + + public int getLegacyInt() { + return mLegacyInt; + } + + @Override + public String toString() { + return "Score(" + mLegacyInt + ")"; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + dest.writeInt(mLegacyInt); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull public static final Creator<NetworkScore> CREATOR = new Creator<>() { + @Override + @NonNull + public NetworkScore createFromParcel(@NonNull final Parcel in) { + return new NetworkScore(in); + } + + @Override + @NonNull + public NetworkScore[] newArray(int size) { + return new NetworkScore[size]; + } + }; + + /** + * A builder for NetworkScore. + */ + public static final class Builder { + private static final int INVALID_LEGACY_INT = Integer.MIN_VALUE; + private int mLegacyInt = INVALID_LEGACY_INT; + + /** + * Sets the legacy int for this score. + * + * Do not rely on this. It will be gone by the time S is released. + * + * @param score the legacy int + * @return this + */ + @NonNull + public Builder setLegacyInt(final int score) { + mLegacyInt = score; + return this; + } + + /** + * Builds this NetworkScore. + * @return The built NetworkScore object. + */ + @NonNull + public NetworkScore build() { + return new NetworkScore(mLegacyInt); + } + } +} diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 4c26e2f33fb2..fa6472ee4a79 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -986,13 +986,13 @@ public abstract class BatteryStats implements Parcelable { public abstract void getDeferredJobsLineLocked(StringBuilder sb, int which); /** - * Returns the battery consumption (in microcoulombs) of the screen while on and uid active, + * Returns the battery consumption (in microcoulombs) of bluetooth for this uid, * derived from on device power measurement data. * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. * * {@hide} */ - public abstract long getScreenOnMeasuredBatteryConsumptionUC(); + public abstract long getBluetoothMeasuredBatteryConsumptionUC(); /** * Returns the battery consumption (in microcoulombs) of the uid's cpu usage, derived from @@ -1004,6 +1004,24 @@ public abstract class BatteryStats implements Parcelable { public abstract long getCpuMeasuredBatteryConsumptionUC(); /** + * Returns the battery consumption (in microcoulombs) of the screen while on and uid active, + * derived from on device power measurement data. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getScreenOnMeasuredBatteryConsumptionUC(); + + /** + * Returns the battery consumption (in microcoulombs) of wifi for this uid, + * derived from on device power measurement data. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getWifiMeasuredBatteryConsumptionUC(); + + /** * Returns the battery consumption (in microcoulombs) used by this uid for each * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}). @@ -2505,11 +2523,29 @@ public abstract class BatteryStats implements Parcelable { }; /** - * Returned value if power data is unavailable + * Returned value if power data is unavailable. + * + * {@hide} + */ + public static final long POWER_DATA_UNAVAILABLE = -1L; + + /** + * Returns the battery consumption (in microcoulombs) of bluetooth, derived from on + * device power measurement data. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. * * {@hide} */ - public static final long POWER_DATA_UNAVAILABLE = -1; + public abstract long getBluetoothMeasuredBatteryConsumptionUC(); + + /** + * Returns the battery consumption (in microcoulombs) of the cpu, derived from on device power + * measurement data. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getCpuMeasuredBatteryConsumptionUC(); /** * Returns the battery consumption (in microcoulombs) of the screen while on, derived from on @@ -2530,13 +2566,13 @@ public abstract class BatteryStats implements Parcelable { public abstract long getScreenDozeMeasuredBatteryConsumptionUC(); /** - * Returns the battery consumption (in microcoulombs) of the cpu, derived from on device power - * measurement data. + * Returns the battery consumption (in microcoulombs) of wifi, derived from on + * device power measurement data. * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. * * {@hide} */ - public abstract long getCpuMeasuredBatteryConsumptionUC(); + public abstract long getWifiMeasuredBatteryConsumptionUC(); /** * Returns the battery consumption (in microcoulombs) that each diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 0d9f715c11d4..a2edc93c6e5e 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -140,7 +140,7 @@ public class Build { */ @UnsupportedAppUsage @TestApi - public static final boolean IS_EMULATOR = getString("ro.kernel.qemu").equals("1"); + public static final boolean IS_EMULATOR = getString("ro.boot.qemu").equals("1"); /** * A hardware serial number, if available. Alphanumeric only, case-insensitive. diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index e75e224d9a6f..ab1f688d60ca 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -16,8 +16,11 @@ package android.os; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.system.ErrnoException; @@ -108,6 +111,7 @@ public class Process { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @SystemApi(client = MODULE_LIBRARIES) public static final int VPN_UID = 1016; /** diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index df4ade09753b..d89c3d591d46 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -21,6 +21,7 @@ import android.util.ArrayMap; import android.util.Slog; import java.io.PrintWriter; +import java.util.function.BiConsumer; import java.util.function.Consumer; /** @@ -354,6 +355,23 @@ public class RemoteCallbackList<E extends IInterface> { } /** + * Performs {@code action} on each callback and associated cookie, calling {@link + * #beginBroadcast()}/{@link #finishBroadcast()} before/after looping. + * + * @hide + */ + public <C> void broadcast(BiConsumer<E, C> action) { + int itemCount = beginBroadcast(); + try { + for (int i = 0; i < itemCount; i++) { + action.accept(getBroadcastItem(i), (C) getBroadcastCookie(i)); + } + } finally { + finishBroadcast(); + } + } + + /** * Returns the number of registered callbacks. Note that the number of registered * callbacks may differ from the value returned by {@link #beginBroadcast()} since * the former returns the number of callbacks registered at the time of the call diff --git a/core/java/android/os/connectivity/WifiActivityEnergyInfo.java b/core/java/android/os/connectivity/WifiActivityEnergyInfo.java index 016cc2f3cdad..ad74a9f5c906 100644 --- a/core/java/android/os/connectivity/WifiActivityEnergyInfo.java +++ b/core/java/android/os/connectivity/WifiActivityEnergyInfo.java @@ -110,7 +110,7 @@ public final class WifiActivityEnergyInfo implements Parcelable { } // Calculate energy used using PowerProfile. PowerProfile powerProfile = new PowerProfile(context); - final double rxIdleCurrent = powerProfile.getAveragePower( + final double idleCurrent = powerProfile.getAveragePower( PowerProfile.POWER_WIFI_CONTROLLER_IDLE); final double rxCurrent = powerProfile.getAveragePower( PowerProfile.POWER_WIFI_CONTROLLER_RX); @@ -121,7 +121,7 @@ public final class WifiActivityEnergyInfo implements Parcelable { return (long) ((txDurationMillis * txCurrent + rxDurationMillis * rxCurrent - + idleDurationMillis * rxIdleCurrent) + + idleDurationMillis * idleCurrent) * voltage); } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index bae36b299247..177e422e7851 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -41,6 +41,7 @@ import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.permission.SplitPermissionInfoParcelable; +import android.location.LocationManager; import android.media.AudioManager; import android.os.Build; import android.os.Handler; @@ -50,12 +51,14 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; import android.util.Log; import android.util.Slog; +import com.android.internal.R; import com.android.internal.annotations.Immutable; import com.android.internal.util.CollectionUtils; @@ -82,6 +85,8 @@ public final class PermissionManager { public static final String KILL_APP_REASON_GIDS_CHANGED = "permission grant or revoke changed gids"; + private static final String SYSTEM_PKG = "android"; + /** * Refuse to install package if groups of permissions are bad * - Permission groups should only be shared between apps sharing a certificate @@ -857,6 +862,23 @@ public final class PermissionManager { } /** + * Check if this package/op combination is exempted from indicators + * @return + * @hide + */ + public static boolean isSpecialCaseShownIndicator(@NonNull Context context, + @NonNull String packageName) { + + if (packageName.equals(SYSTEM_PKG)) { + return false; + } + + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "permissions_hub_2_enabled", + false) + || packageName.equals(context.getString(R.string.config_systemSpeechRecognizer)) + || context.getSystemService(LocationManager.class).isProviderPackage(packageName); + } + /** * Gets the list of packages that have permissions that specified * {@code requestDontAutoRevokePermissions=true} in their * {@code application} manifest declaration. diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index 7e3a0f30e75c..80a3e1693ab1 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -31,40 +31,25 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_W import static android.media.AudioSystem.MODE_IN_COMMUNICATION; import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; -import android.Manifest; import android.annotation.NonNull; import android.app.AppOpsManager; -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; import android.content.pm.ApplicationInfo; -import android.content.pm.Attribution; -import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.icu.text.ListFormatter; -import android.location.LocationManager; import android.media.AudioManager; import android.os.Process; import android.os.UserHandle; import android.provider.DeviceConfig; -import android.provider.Settings; -import android.speech.RecognitionService; -import android.speech.RecognizerIntent; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; - -import com.android.internal.R; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; /** * A helper which gets all apps which have used microphone, camera, and possible location @@ -165,7 +150,7 @@ public class PermissionUsageHelper { * Constructor for PermissionUsageHelper * @param context The context from which to derive the package information */ - public PermissionUsageHelper(Context context) { + public PermissionUsageHelper(@NonNull Context context) { mContext = context; mPkgManager = context.getPackageManager(); mAppOpsManager = context.getSystemService(AppOpsManager.class); @@ -180,26 +165,10 @@ public class PermissionUsageHelper { return mUserContexts.get(user); } - // TODO ntmyren: Replace this with better check if this moves beyond teamfood - private boolean isAppPredictor(String packageName, UserHandle user) { - return shouldShowPermissionsHub() && getUserContext(user).getPackageManager() - .checkPermission(Manifest.permission.MANAGE_APP_PREDICTIONS, packageName) - == PackageManager.PERMISSION_GRANTED; - } - - private boolean isSpeechRecognizerUsage(String op, String packageName) { - if (!OPSTR_RECORD_AUDIO.equals(op)) { - return false; - } - - return packageName.equals( - mContext.getString(R.string.config_systemSpeechRecognizer)); - } - /** * @see PermissionManager.getIndicatorAppOpUsageData */ - public List<PermGroupUsage> getOpUsageData(boolean isMicMuted) { + public @NonNull List<PermGroupUsage> getOpUsageData(boolean isMicMuted) { List<PermGroupUsage> usages = new ArrayList<>(); if (!shouldShowIndicators()) { @@ -215,9 +184,6 @@ public class PermissionUsageHelper { } Map<String, List<OpUsage>> rawUsages = getOpUsages(ops); - Set<List<PackageAttribution>> proxyChains = getProxyChains(rawUsages.get(MICROPHONE)); - Map<PackageAttribution, CharSequence> packagesWithAttributionLabels = - getTrustedAttributions(rawUsages.get(MICROPHONE), proxyChains); ArrayList<String> usedPermGroups = new ArrayList<>(rawUsages.keySet()); @@ -245,15 +211,8 @@ public class PermissionUsageHelper { boolean isPhone = false; String permGroup = usedPermGroups.get(permGroupNum); - Map<PackageAttribution, CharSequence> pkgAttrLabels = packagesWithAttributionLabels; - Set<List<PackageAttribution>> proxies = proxyChains; - if (!MICROPHONE.equals(permGroup)) { - pkgAttrLabels = new ArrayMap<>(); - proxies = new ArraySet<>(); - } - - List<OpUsage> permUsages = removeDuplicatesAndProxies(rawUsages.get(permGroup), - pkgAttrLabels.keySet(), proxies); + ArrayMap<OpUsage, CharSequence> usagesWithLabels = + getUniqueUsagesWithLabels(rawUsages.get(permGroup)); if (permGroup.equals(OPSTR_PHONE_CALL_MICROPHONE)) { isPhone = true; @@ -263,11 +222,11 @@ public class PermissionUsageHelper { permGroup = CAMERA; } - for (int usageNum = 0; usageNum < permUsages.size(); usageNum++) { - OpUsage usage = permUsages.get(usageNum); + for (int usageNum = 0; usageNum < usagesWithLabels.size(); usageNum++) { + OpUsage usage = usagesWithLabels.keyAt(usageNum); usages.add(new PermGroupUsage(usage.packageName, usage.uid, permGroup, usage.lastAccessTime, usage.isRunning, isPhone, - packagesWithAttributionLabels.get(usage.toPackageAttr()))); + usagesWithLabels.valueAt(usageNum))); } } @@ -297,7 +256,7 @@ public class PermissionUsageHelper { long recentThreshold = getRecentThreshold(now); long runningThreshold = getRunningThreshold(now); int opFlags = OP_FLAGS_ALL_TRUSTED; - Map<String, Map<PackageAttribution, OpUsage>> usages = new ArrayMap<>(); + Map<String, Map<Integer, OpUsage>> usages = new ArrayMap<>(); int numPkgOps = ops.size(); for (int pkgOpNum = 0; pkgOpNum < numPkgOps; pkgOpNum++) { @@ -326,9 +285,7 @@ public class PermissionUsageHelper { if (packageName.equals(SYSTEM_PKG) || (!shouldShowPermissionsHub() - && !isUserSensitive(packageName, user, op) - && !isLocationProvider(packageName, user) - && !isSpeechRecognizerUsage(op, packageName))) { + && !isUserSensitive(packageName, user, op))) { continue; } @@ -339,20 +296,20 @@ public class PermissionUsageHelper { AppOpsManager.OpEventProxyInfo proxy = attrOpEntry.getLastProxyInfo(opFlags); if (proxy != null && proxy.getPackageName() != null) { proxyUsage = new OpUsage(proxy.getPackageName(), proxy.getAttributionTag(), - proxy.getUid(), lastAccessTime, isRunning, null); + op, proxy.getUid(), lastAccessTime, isRunning, null); } String permGroupName = getGroupForOp(op); - OpUsage usage = new OpUsage(packageName, attributionTag, uid, + OpUsage usage = new OpUsage(packageName, attributionTag, op, uid, lastAccessTime, isRunning, proxyUsage); - PackageAttribution packageAttr = usage.toPackageAttr(); + Integer packageAttr = usage.getPackageAttrHash(); if (!usages.containsKey(permGroupName)) { - ArrayMap<PackageAttribution, OpUsage> map = new ArrayMap<>(); + ArrayMap<Integer, OpUsage> map = new ArrayMap<>(); map.put(packageAttr, usage); usages.put(permGroupName, map); } else { - Map<PackageAttribution, OpUsage> permGroupUsages = + Map<Integer, OpUsage> permGroupUsages = usages.get(permGroupName); if (!permGroupUsages.containsKey(packageAttr)) { permGroupUsages.put(packageAttr, usage); @@ -374,380 +331,119 @@ public class PermissionUsageHelper { return flattenedUsages; } - /** - * Take the list of all usages, figure out any proxy chains, get all possible special - * attribution labels, and figure out which usages need to show a special label, if any. - * - * @param usages The raw permission usages - * - * @return A map of package + attribution (in the form of a PackageAttribution object) to - * trusted attribution label, if there is one - */ - private ArrayMap<PackageAttribution, CharSequence> getTrustedAttributions( - List<OpUsage> usages, Set<List<PackageAttribution>> proxyChains) { - ArrayMap<PackageAttribution, CharSequence> attributions = new ArrayMap<>(); - if (usages == null) { - return attributions; - } - - Map<PackageAttribution, CharSequence> trustedLabels = - getTrustedAttributionLabels(usages); - - for (List<PackageAttribution> chain : proxyChains) { - // If this chain is empty, or has only one link, then do not show any special labels - if (chain.size() <= 1) { - continue; - } - - // If the last link in the chain is not user sensitive, do not show it. - boolean lastLinkIsUserSensitive = false; - for (int i = 0; i < usages.size(); i++) { - PackageAttribution lastLink = chain.get(chain.size() - 1); - if (lastLink.equals(usages.get(i).toPackageAttr())) { - lastLinkIsUserSensitive = true; - break; - } - } - if (!lastLinkIsUserSensitive) { - continue; - } - - List<CharSequence> labels = new ArrayList<>(); - for (int i = 0; i < chain.size(); i++) { - // If this is the last link in the proxy chain, assign it the series of labels - // Else, if it has a special label, add that label - // Else, if there are no other apps in the remaining part of the chain which - // have the same package name, add the app label - // If it is not the last link in the chain, remove its attribution - PackageAttribution attr = chain.get(i); - CharSequence trustedLabel = trustedLabels.get(attr); - if (i == chain.size() - 1) { - attributions.put(attr, formatLabelList(labels)); - } else if (trustedLabel != null && !labels.contains(trustedLabel)) { - labels.add(trustedLabel); - trustedLabels.remove(attr); - } else { - boolean remainingChainHasPackage = false; - for (int attrNum = i + 1; attrNum < chain.size() - 1; attrNum++) { - if (chain.get(i).packageName.equals(attr.packageName)) { - remainingChainHasPackage = true; - break; - } - } - if (!remainingChainHasPackage) { - try { - ApplicationInfo appInfo = mPkgManager.getApplicationInfoAsUser( - attr.packageName, 0, attr.getUser()); - CharSequence appLabel = appInfo.loadLabel( - getUserContext(attr.getUser()).getPackageManager()); - labels.add(appLabel); - } catch (PackageManager.NameNotFoundException e) { - // Do nothing - } - } - } - } - } - - for (PackageAttribution attr : trustedLabels.keySet()) { - attributions.put(attr, trustedLabels.get(attr)); - } - - return attributions; - } - private CharSequence formatLabelList(List<CharSequence> labels) { return ListFormatter.getInstance().format(labels); } - /** - * Get all chains of proxy usages. A proxy chain is defined as one usage at the root, then - * further proxy usages, where the app and attribution tag of the proxy in the proxy usage - * matches the previous usage in the chain. - * - * @param usages The permission usages - * - * @return A set of lists of package attributions. One list represents a chain of proxy usages, - * with the start of the chain (the usage without a proxy) at position 0, and each usage down - * the chain has the previous one listed as a proxy usage. - */ - private Set<List<PackageAttribution>> getProxyChains(List<OpUsage> usages) { - if (usages == null) { - return new ArraySet<>(); - } + private ArrayMap<OpUsage, CharSequence> getUniqueUsagesWithLabels(List<OpUsage> usages) { + ArrayMap<OpUsage, CharSequence> usagesAndLabels = new ArrayMap<>(); - ArrayMap<PackageAttribution, ArrayList<PackageAttribution>> proxyChains = new ArrayMap<>(); - // map of usages that still need to be removed, or added to a chain - ArrayMap<PackageAttribution, OpUsage> remainingUsages = new ArrayMap<>(); - // map of usage.proxy -> usage, telling us if a usage is a proxy - ArrayMap<PackageAttribution, PackageAttribution> proxies = new ArrayMap<>(); + if (usages == null) { + return usagesAndLabels; + } + + ArrayMap<Integer, OpUsage> allUsages = new ArrayMap<>(); + // map of uid -> most recent non-proxy-related usage for that uid. + ArrayMap<Integer, OpUsage> mostRecentUsages = new ArrayMap<>(); + // set of all uids involved in a proxy usage + ArraySet<Integer> proxyUids = new ArraySet<>(); + // map of usage -> list of proxy app labels + ArrayMap<OpUsage, ArrayList<CharSequence>> proxyLabels = new ArrayMap<>(); + // map of usage.proxy hash -> usage hash, telling us if a usage is a proxy + ArrayMap<Integer, OpUsage> proxies = new ArrayMap<>(); for (int i = 0; i < usages.size(); i++) { OpUsage usage = usages.get(i); - remainingUsages.put(usage.toPackageAttr(), usage); + allUsages.put(usage.getPackageAttrHash(), usage); if (usage.proxy != null) { - proxies.put(usage.proxy.toPackageAttr(), usage.toPackageAttr()); + proxies.put(usage.proxy.getPackageAttrHash(), usage); } } - // find all possible end points for chains - List<PackageAttribution> keys = new ArrayList<>(remainingUsages.keySet()); - for (int usageNum = 0; usageNum < remainingUsages.size(); usageNum++) { - OpUsage usage = remainingUsages.get(keys.get(usageNum)); + // find all possible end points for chains, and find the most recent of the rest of the uses + for (int usageNum = 0; usageNum < usages.size(); usageNum++) { + OpUsage usage = usages.get(usageNum); if (usage == null) { continue; } - PackageAttribution usageAttr = usage.toPackageAttr(); + + int usageAttr = usage.getPackageAttrHash(); // If this usage has a proxy, but is not a proxy, it is the end of a chain. - // If it has no proxy, and isn't a proxy, remove it. if (!proxies.containsKey(usageAttr) && usage.proxy != null) { - ArrayList<PackageAttribution> proxyList = new ArrayList<>(); - proxyList.add(usageAttr); - proxyChains.put(usageAttr, proxyList); - } else if (!proxies.containsKey(usageAttr) && usage.proxy == null) { - remainingUsages.remove(keys.get(usageNum)); + proxyLabels.put(usage, new ArrayList<>()); + proxyUids.add(usage.uid); + } + if (!mostRecentUsages.containsKey(usage.uid) || usage.lastAccessTime + > mostRecentUsages.get(usage.uid).lastAccessTime) { + mostRecentUsages.put(usage.uid, usage); } } - // assemble the chains in reverse order, then invert them - for (int numStart = 0; numStart < proxyChains.size(); numStart++) { - PackageAttribution currPackageAttr = proxyChains.keyAt(numStart); - ArrayList<PackageAttribution> proxyChain = proxyChains.get(currPackageAttr); - OpUsage currentUsage = remainingUsages.get(currPackageAttr); - if (currentUsage == null || proxyChain == null) { + // get all the proxy labels + for (int numStart = 0; numStart < proxyLabels.size(); numStart++) { + OpUsage start = proxyLabels.keyAt(numStart); + // Remove any non-proxy usage for the starting uid + mostRecentUsages.remove(start.uid); + OpUsage currentUsage = proxyLabels.keyAt(numStart); + ArrayList<CharSequence> proxyLabelList = proxyLabels.get(currentUsage); + if (currentUsage == null || proxyLabelList == null) { continue; } + int iterNum = 0; + int maxUsages = allUsages.size(); while (currentUsage.proxy != null) { - currPackageAttr = currentUsage.proxy.toPackageAttr(); - currentUsage = remainingUsages.get(currPackageAttr); - - boolean invalidState = false; - for (int chainNum = 0; chainNum < proxyChain.size(); chainNum++) { - if (currentUsage == null || proxyChain.get(chainNum).equals(currPackageAttr)) { - // either our current value is not in the usage list, or we have a cycle - invalidState = true; - break; - } - } - - if (invalidState) { - break; - } - - proxyChain.add(currPackageAttr); - } - // invert the lists, so the element without a proxy is first on the list - Collections.reverse(proxyChain); - } - return new ArraySet<>(proxyChains.values()); - } - - /** - * Gets all trusted proxied voice IME and voice recognition microphone uses, and get the - * label needed to display with it, as well as information about the proxy whose label is being - * shown, if applicable. - * - * @param usages The permission usages - * - * @return A map of package attribution -> the attribution label for that package attribution, - * if applicable - */ - private Map<PackageAttribution, CharSequence> getTrustedAttributionLabels( - List<OpUsage> usages) { - List<UserHandle> users = new ArrayList<>(); - for (int i = 0; i < usages.size(); i++) { - UserHandle user = UserHandle.getUserHandleForUid(usages.get(i).uid); - if (!users.contains(user)) { - users.add(user); - } - } - - Map<PackageAttribution, CharSequence> trustedLabels = new ArrayMap<>(); - for (int userNum = 0; userNum < users.size(); userNum++) { - UserHandle user = users.get(userNum); - Context userContext = mContext.createContextAsUser(user, 0); - - // Get all voice IME labels - Map<String, CharSequence> voiceInputs = new ArrayMap<>(); - List<InputMethodInfo> inputs = userContext.getSystemService(InputMethodManager.class) - .getEnabledInputMethodList(); - for (int inputNum = 0; inputNum < inputs.size(); inputNum++) { - InputMethodInfo input = inputs.get(inputNum); - for (int subtypeNum = 0; subtypeNum < input.getSubtypeCount(); subtypeNum++) { - if (VOICE_IME_SUBTYPE.equals(input.getSubtypeAt(subtypeNum).getMode())) { - voiceInputs.put(input.getPackageName(), input.getServiceInfo() - .loadUnsafeLabel(userContext.getPackageManager())); + if (allUsages.containsKey(currentUsage.proxy.getPackageAttrHash())) { + currentUsage = allUsages.get(currentUsage.proxy.getPackageAttrHash()); + } else { + // We are missing the proxy usage. This may be because it's a one-step trusted + // proxy. Check if we should show the proxy label, and show it, if so. + OpUsage proxy = currentUsage.proxy; + if (PermissionManager.isSpecialCaseShownIndicator(mContext, proxy.packageName) + || isUserSensitive(proxy.packageName, proxy.getUser(), proxy.op)) { + currentUsage = proxy; + // We've effectively added one usage, so increment the max number of usages + maxUsages++; + } else { break; } } - } - // Get the currently selected recognizer from the secure setting - String recognitionPackageName = Settings.Secure.getString( - userContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE); - if (recognitionPackageName == null) { - continue; - } - recognitionPackageName = - ComponentName.unflattenFromString(recognitionPackageName).getPackageName(); - Map<String, CharSequence> recognizers = new ArrayMap<>(); - List<ResolveInfo> availableRecognizers = mPkgManager.queryIntentServicesAsUser( - new Intent(RecognitionService.SERVICE_INTERFACE), PackageManager.GET_META_DATA, - user.getIdentifier()); - for (int recogNum = 0; recogNum < availableRecognizers.size(); recogNum++) { - ResolveInfo info = availableRecognizers.get(recogNum); - if (recognitionPackageName.equals(info.serviceInfo.packageName)) { - recognizers.put(recognitionPackageName, info.serviceInfo.loadUnsafeLabel( - userContext.getPackageManager())); - } - } - Map<String, CharSequence> recognizerIntents = new ArrayMap<>(); - List<ResolveInfo> availableRecognizerIntents = mPkgManager.queryIntentActivitiesAsUser( - new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), - PackageManager.GET_META_DATA, user); - for (int recogNum = 0; recogNum < availableRecognizerIntents.size(); recogNum++) { - ResolveInfo info = availableRecognizerIntents.get(recogNum); - if (info.activityInfo == null) { - continue; - } - String pkgName = info.activityInfo.packageName; - if (recognitionPackageName.equals(pkgName) && recognizers.containsKey(pkgName)) { - recognizerIntents.put(pkgName, recognizers.get(pkgName)); - } - } - for (int usageNum = 0; usageNum < usages.size(); usageNum++) { - setTrustedAttrsForAccess(usages.get(usageNum), user, false, voiceInputs, - trustedLabels); - setTrustedAttrsForAccess(usages.get(usageNum), user, false, recognizerIntents, - trustedLabels); - setTrustedAttrsForAccess(usages.get(usageNum), user, true, recognizers, - trustedLabels); - } - } - - return trustedLabels; - } - - private void setTrustedAttrsForAccess(OpUsage opUsage, UserHandle currUser, boolean getProxy, - Map<String, CharSequence> trustedMap, Map<PackageAttribution, CharSequence> toSetMap) { - OpUsage usage = opUsage; - if (getProxy) { - usage = opUsage.proxy; - } - - if (usage == null || !usage.getUser().equals(currUser) - || !trustedMap.containsKey(usage.packageName)) { - return; - } - - CharSequence label = getAttributionLabel(usage); - if (trustedMap.get(usage.packageName).equals(label)) { - toSetMap.put(opUsage.toPackageAttr(), label); - } - } - - private CharSequence getAttributionLabel(OpUsage usage) { - if (usage.attributionTag == null) { - return null; - } - - PackageInfo pkgInfo; - try { - pkgInfo = mPkgManager.getPackageInfoAsUser(usage.packageName, - PackageManager.GET_ATTRIBUTIONS, usage.getUser().getIdentifier()); - if (pkgInfo.attributions == null || pkgInfo.attributions.length == 0) { - return null; - } - for (int attrNum = 0; attrNum < pkgInfo.attributions.length; attrNum++) { - Attribution attr = pkgInfo.attributions[attrNum]; - if (usage.attributionTag.equals(attr.getTag())) { - return mContext.createPackageContextAsUser(usage.packageName, 0, - usage.getUser()).getString(attr.getLabel()); - } - } - return null; - } catch (PackageManager.NameNotFoundException e) { - return null; - } - } - - /** - * If we have multiple usages of a - * @param rawUsages The list of all usages that we wish to - * @param specialAttributions A set of all usages that have a special label - * @param proxies A list of proxy chains- all links but the last on the chain should be removed, - * if the last link has a special label - * @return A list of usages without duplicates or proxy usages. - */ - private List<OpUsage> removeDuplicatesAndProxies(List<OpUsage> rawUsages, - Set<PackageAttribution> specialAttributions, - Set<List<PackageAttribution>> proxies) { - List<OpUsage> deDuped = new ArrayList<>(); - if (rawUsages == null) { - return deDuped; - } - - List<PackageAttribution> toRemoveProxies = new ArrayList<>(); - for (List<PackageAttribution> proxyList: proxies) { - PackageAttribution lastLink = proxyList.get(proxyList.size() - 1); - if (!specialAttributions.contains(lastLink)) { - continue; - } - for (int proxyNum = 0; proxyNum < proxyList.size(); proxyNum++) { - if (!proxyList.get(proxyNum).equals(lastLink)) { - toRemoveProxies.add(proxyList.get(proxyNum)); - } - } - } - - for (int usageNum = 0; usageNum < rawUsages.size(); usageNum++) { - OpUsage usage = rawUsages.get(usageNum); - - // If this attribution is a proxy, remove it - if (toRemoveProxies.contains(usage.toPackageAttr())) { - continue; - } - - // If this attribution has a special attribution, do not remove it - if (specialAttributions.contains(usage.toPackageAttr())) { - deDuped.add(usage); - continue; - } - - - // Search the rest of the list for usages with the same UID. If this is the most recent - // usage for that uid, keep it. Otherwise, remove it - boolean isMostRecentForUid = true; - for (int otherUsageNum = 0; otherUsageNum < rawUsages.size(); otherUsageNum++) { - // Do not compare this usage to itself - if (otherUsageNum == usageNum) { - continue; + if (currentUsage == null || iterNum == maxUsages + || currentUsage.getPackageAttrHash() == start.getPackageAttrHash()) { + // We have an invalid state, or a cycle, so break + break; } - OpUsage otherUsage = rawUsages.get(otherUsageNum); - if (otherUsage.uid == usage.uid) { - if (otherUsage.isRunning && !usage.isRunning) { - isMostRecentForUid = false; - } else if (usage.isRunning - && otherUsage.lastAccessTime >= usage.lastAccessTime) { - isMostRecentForUid = false; - } else if (otherUsage.lastAccessTime >= usage.lastAccessTime) { - isMostRecentForUid = false; - } - - if (!isMostRecentForUid) { - break; + proxyUids.add(currentUsage.uid); + try { + PackageManager userPkgManager = + getUserContext(currentUsage.getUser()).getPackageManager(); + ApplicationInfo appInfo = userPkgManager.getApplicationInfo( + currentUsage.packageName, 0); + CharSequence appLabel = appInfo.loadLabel(userPkgManager); + // If we don't already have the app label, and it's not the same as the main + // app, add it + if (!proxyLabelList.contains(appLabel) + && !currentUsage.packageName.equals(start.packageName)) { + proxyLabelList.add(appLabel); } + } catch (PackageManager.NameNotFoundException e) { + // Ignore } + iterNum++; } + usagesAndLabels.put(start, + proxyLabelList.isEmpty() ? null : formatLabelList(proxyLabelList)); + } - if (isMostRecentForUid) { - deDuped.add(usage); + for (int uid : mostRecentUsages.keySet()) { + if (!proxyUids.contains(uid)) { + usagesAndLabels.put(mostRecentUsages.get(uid), null); } } - return deDuped; + return usagesAndLabels; } private boolean isUserSensitive(String packageName, UserHandle user, String op) { @@ -763,11 +459,6 @@ public class PermissionUsageHelper { return (permFlags & FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0; } - private boolean isLocationProvider(String packageName, UserHandle user) { - return getUserContext(user) - .getSystemService(LocationManager.class).isProviderPackage(packageName); - } - /** * Represents the usage of an App op by a particular package and attribution */ @@ -775,62 +466,45 @@ public class PermissionUsageHelper { public final String packageName; public final String attributionTag; + public final String op; public final int uid; public final long lastAccessTime; public final OpUsage proxy; public final boolean isRunning; - OpUsage(String packageName, String attributionTag, int uid, long lastAccessTime, + OpUsage(String packageName, String attributionTag, String op, int uid, long lastAccessTime, boolean isRunning, OpUsage proxy) { - this.isRunning = isRunning; this.packageName = packageName; this.attributionTag = attributionTag; + this.op = op; this.uid = uid; this.lastAccessTime = lastAccessTime; + this.isRunning = isRunning; this.proxy = proxy; } - public PackageAttribution toPackageAttr() { - return new PackageAttribution(packageName, attributionTag, uid); - } - public UserHandle getUser() { return UserHandle.getUserHandleForUid(uid); } - } - /** - * A unique identifier for one package attribution, made up of attribution tag, package name - * and user - */ - private static class PackageAttribution { - public final String packageName; - public final String attributionTag; - public final int uid; + public int getPackageAttrHash() { + return Objects.hash(packageName, attributionTag, uid); + } - PackageAttribution(String packageName, String attributionTag, int uid) { - this.packageName = packageName; - this.attributionTag = attributionTag; - this.uid = uid; + @Override + public int hashCode() { + return Objects.hash(packageName, attributionTag, op, uid, lastAccessTime, isRunning); } @Override public boolean equals(Object obj) { - if (!(obj instanceof PackageAttribution)) { + if (!(obj instanceof OpUsage)) { return false; } - PackageAttribution other = (PackageAttribution) obj; + OpUsage other = (OpUsage) obj; return Objects.equals(packageName, other.packageName) && Objects.equals(attributionTag, - other.attributionTag) && Objects.equals(uid, other.uid); - } - - @Override - public int hashCode() { - return Objects.hash(packageName, attributionTag, uid); - } - - public UserHandle getUser() { - return UserHandle.getUserHandleForUid(uid); + other.attributionTag) && Objects.equals(op, other.op) && uid == other.uid + && lastAccessTime == other.lastAccessTime && isRunning == other.isRunning; } } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 5d139d971328..f0b22a923e0f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4126,7 +4126,6 @@ public final class Settings { * unset or a match is not made, only the standard color modes will be restored. * @hide */ - @Readable public static final String DISPLAY_COLOR_MODE_VENDOR_HINT = "display_color_mode_vendor_hint"; @@ -6163,7 +6162,6 @@ public final class Settings { * * @hide */ - @Readable public static final String CAMERA_AUTOROTATE = "camera_autorotate"; /** @@ -6724,7 +6722,6 @@ public final class Settings { * android.app.timezonedetector.TimeZoneDetector#updateConfiguration} to update. * @hide */ - @Readable public static final String LOCATION_TIME_ZONE_DETECTION_ENABLED = "location_time_zone_detection_enabled"; @@ -7040,6 +7037,15 @@ public final class Settings { "enabled_accessibility_services"; /** + * List of the notified non-accessibility category accessibility services. + * + * @hide + */ + @Readable + public static final String NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES = + "notified_non_accessibility_category_services"; + + /** * List of the accessibility services to which the user has granted * permission to put the device into touch exploration mode. * @@ -7515,7 +7521,6 @@ public final class Settings { * * @hide */ - @Readable public static final String REDUCE_BRIGHT_COLORS_ACTIVATED = "reduce_bright_colors_activated"; @@ -7525,7 +7530,6 @@ public final class Settings { * * @hide */ - @Readable public static final String REDUCE_BRIGHT_COLORS_LEVEL = "reduce_bright_colors_level"; @@ -7534,7 +7538,6 @@ public final class Settings { * * @hide */ - @Readable public static final String REDUCE_BRIGHT_COLORS_PERSIST_ACROSS_REBOOTS = "reduce_bright_colors_persist_across_reboots"; @@ -8245,7 +8248,6 @@ public final class Settings { * @see #MATCH_CONTENT_FRAMERATE_ALWAYS * @hide */ - @Readable public static final String MATCH_CONTENT_FRAME_RATE = "match_content_frame_rate"; @@ -8373,7 +8375,6 @@ public final class Settings { * {@link Display.STATE_OFF} and {@link Display.STATE_DOZE}. * @hide */ - @Readable public static final String DOZE_QUICK_PICKUP_GESTURE = "doze_quick_pickup_gesture"; /** @@ -8479,7 +8480,6 @@ public final class Settings { * For user preference if swipe bottom to expand notification gesture enabled. * @hide */ - @Readable public static final String SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED = "swipe_bottom_to_notification_enabled"; @@ -8487,28 +8487,24 @@ public final class Settings { * For user preference if One-Handed Mode enabled. * @hide */ - @Readable public static final String ONE_HANDED_MODE_ENABLED = "one_handed_mode_enabled"; /** * For user preference if One-Handed Mode timeout. * @hide */ - @Readable public static final String ONE_HANDED_MODE_TIMEOUT = "one_handed_mode_timeout"; /** * For user taps app to exit One-Handed Mode. * @hide */ - @Readable public static final String TAPS_APP_TO_EXIT = "taps_app_to_exit"; /** * Internal use, one handed mode tutorial showed times. * @hide */ - @Readable public static final String ONE_HANDED_TUTORIAL_SHOW_COUNT = "one_handed_tutorial_show_count"; @@ -8534,7 +8530,6 @@ public final class Settings { * The last computed night mode bool the last time the phone was on * @hide */ - @Readable public static final String UI_NIGHT_MODE_LAST_COMPUTED = "ui_night_mode_last_computed"; /** @@ -8953,7 +8948,6 @@ public final class Settings { * * @hide */ - @Readable public static final String EMERGENCY_GESTURE_ENABLED = "emergency_gesture_enabled"; /** @@ -8961,7 +8955,6 @@ public final class Settings { * * @hide */ - @Readable public static final String EMERGENCY_GESTURE_SOUND_ENABLED = "emergency_gesture_sound_enabled"; @@ -9694,7 +9687,6 @@ public final class Settings { * @see Settings.Secure#MEDIA_CONTROLS_RESUME * @hide */ - @Readable public static final String MEDIA_CONTROLS_RESUME_BLOCKED = "qs_media_resumption_blocked"; /** @@ -9742,7 +9734,6 @@ public final class Settings { * @hide */ @TestApi - @Readable public static final String ACCESSIBILITY_MAGNIFICATION_CAPABILITY = "accessibility_magnification_capability"; @@ -9752,7 +9743,6 @@ public final class Settings { * * @hide */ - @Readable public static final String ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT = "accessibility_show_window_magnification_prompt"; @@ -9769,7 +9759,6 @@ public final class Settings { * @see #ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU * @hide */ - @Readable public static final String ACCESSIBILITY_BUTTON_MODE = "accessibility_button_mode"; @@ -9798,7 +9787,6 @@ public final class Settings { * * @hide */ - @Readable public static final String ACCESSIBILITY_FLOATING_MENU_SIZE = "accessibility_floating_menu_size"; @@ -9811,7 +9799,6 @@ public final class Settings { * * @hide */ - @Readable public static final String ACCESSIBILITY_FLOATING_MENU_ICON_TYPE = "accessibility_floating_menu_icon_type"; @@ -9820,7 +9807,6 @@ public final class Settings { * * @hide */ - @Readable public static final String ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED = "accessibility_floating_menu_fade_enabled"; @@ -9830,7 +9816,6 @@ public final class Settings { * * @hide */ - @Readable public static final String ACCESSIBILITY_FLOATING_MENU_OPACITY = "accessibility_floating_menu_opacity"; @@ -9839,7 +9824,6 @@ public final class Settings { * * @hide */ - @Readable public static final String ADAPTIVE_CONNECTIVITY_ENABLED = "adaptive_connectivity_enabled"; /** @@ -9863,7 +9847,6 @@ public final class Settings { * * @hide */ - @Readable public static final String ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS = "reminder_exp_learning_time_elapsed"; @@ -9872,7 +9855,6 @@ public final class Settings { * * @hide */ - @Readable public static final String ASSIST_HANDLES_LEARNING_EVENT_COUNT = "reminder_exp_learning_event_count"; @@ -10581,7 +10563,6 @@ public final class Settings { * * @hide */ - @Readable public static final String DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH = "wm_display_settings_path"; @@ -10761,7 +10742,6 @@ public final class Settings { * * @hide */ - @Readable public static final String HDMI_CONTROL_SEND_STANDBY_ON_SLEEP = "hdmi_control_send_standby_on_sleep"; @@ -14439,7 +14419,6 @@ public final class Settings { * * @hide */ - @Readable public static final String EUICC_SWITCH_SLOT_TIMEOUT_MILLIS = "euicc_switch_slot_timeout_millis"; @@ -14449,7 +14428,6 @@ public final class Settings { * * @hide */ - @Readable public static final String ENABLE_MULTI_SLOT_TIMEOUT_MILLIS = "enable_multi_slot_timeout_millis"; @@ -14573,7 +14551,6 @@ public final class Settings { * * @hide */ - @Readable public static final String FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT = "force_non_debuggable_final_build_for_compat"; @@ -14653,7 +14630,6 @@ public final class Settings { * * @hide */ - @Readable public static final String ONE_HANDED_KEYGUARD_SIDE = "one_handed_keyguard_side"; /** @@ -15336,7 +15312,6 @@ public final class Settings { * The value 1 - enable, 0 - disable * @hide */ - @Readable public static final String NOTIFICATION_FEEDBACK_ENABLED = "notification_feedback_enabled"; /** @@ -15579,7 +15554,6 @@ public final class Settings { * * @hide */ - @Readable public static final String GNSS_SATELLITE_BLOCKLIST = "gnss_satellite_blocklist"; /** @@ -15784,7 +15758,6 @@ public final class Settings { * 1: Enabled * @hide */ - @Readable public static final String SHOW_PEOPLE_SPACE = "show_people_space"; /** @@ -15795,7 +15768,6 @@ public final class Settings { * 2: All conversations * @hide */ - @Readable public static final String PEOPLE_SPACE_CONVERSATION_TYPE = "people_space_conversation_type"; @@ -15806,7 +15778,6 @@ public final class Settings { * 1: Enabled * @hide */ - @Readable public static final String SHOW_NEW_NOTIF_DISMISS = "show_new_notif_dismiss"; /** @@ -15821,7 +15792,6 @@ public final class Settings { * 1: Enabled (All apps will receive the new rules) * @hide */ - @Readable public static final String BACKPORT_S_NOTIF_RULES = "backport_s_notif_rules"; /** @@ -15836,7 +15806,6 @@ public final class Settings { * <p>See {@link android.app.Notification.DevFlags} for more details. * @hide */ - @Readable public static final String FULLY_CUSTOM_VIEW_NOTIF_DECORATION = "fully_custom_view_notif_decoration"; @@ -15850,7 +15819,6 @@ public final class Settings { * <p>See {@link android.app.Notification.DevFlags} for more details. * @hide */ - @Readable public static final String DECORATED_CUSTOM_VIEW_NOTIF_DECORATION = "decorated_custom_view_notif_decoration"; @@ -15867,7 +15835,6 @@ public final class Settings { * * @hide */ - @Readable public static final String BLOCK_UNTRUSTED_TOUCHES_MODE = "block_untrusted_touches"; /** @@ -15893,7 +15860,6 @@ public final class Settings { * * @hide */ - @Readable public static final String MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH = "maximum_obscuring_opacity_for_touch"; @@ -15906,7 +15872,6 @@ public final class Settings { * 1: enabled * @hide */ - @Readable public static final String RESTRICTED_NETWORKING_MODE = "restricted_networking_mode"; } diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index e3d0741b4603..6147c58867a6 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -107,10 +107,12 @@ public final class Dataset implements Parcelable { private final ArrayList<AutofillValue> mFieldValues; private final ArrayList<RemoteViews> mFieldPresentations; private final ArrayList<InlinePresentation> mFieldInlinePresentations; + private final ArrayList<InlinePresentation> mFieldInlineTooltipPresentations; private final ArrayList<DatasetFieldFilter> mFieldFilters; @Nullable private final ClipData mFieldContent; private final RemoteViews mPresentation; @Nullable private final InlinePresentation mInlinePresentation; + @Nullable private final InlinePresentation mInlineTooltipPresentation; private final IntentSender mAuthentication; @Nullable String mId; @@ -119,10 +121,12 @@ public final class Dataset implements Parcelable { mFieldValues = builder.mFieldValues; mFieldPresentations = builder.mFieldPresentations; mFieldInlinePresentations = builder.mFieldInlinePresentations; + mFieldInlineTooltipPresentations = builder.mFieldInlineTooltipPresentations; mFieldFilters = builder.mFieldFilters; mFieldContent = builder.mFieldContent; mPresentation = builder.mPresentation; mInlinePresentation = builder.mInlinePresentation; + mInlineTooltipPresentation = builder.mInlineTooltipPresentation; mAuthentication = builder.mAuthentication; mId = builder.mId; } @@ -154,6 +158,14 @@ public final class Dataset implements Parcelable { } /** @hide */ + public @Nullable InlinePresentation getFieldInlineTooltipPresentation(int index) { + final InlinePresentation inlineTooltipPresentation = + mFieldInlineTooltipPresentations.get(index); + return inlineTooltipPresentation != null + ? inlineTooltipPresentation : mInlineTooltipPresentation; + } + + /** @hide */ public @Nullable DatasetFieldFilter getFilter(int index) { return mFieldFilters.get(index); } @@ -209,6 +221,10 @@ public final class Dataset implements Parcelable { if (mFieldInlinePresentations != null) { builder.append(", fieldInlinePresentations=").append(mFieldInlinePresentations.size()); } + if (mFieldInlineTooltipPresentations != null) { + builder.append(", fieldInlineTooltipInlinePresentations=").append( + mFieldInlineTooltipPresentations.size()); + } if (mFieldFilters != null) { builder.append(", fieldFilters=").append(mFieldFilters.size()); } @@ -218,6 +234,9 @@ public final class Dataset implements Parcelable { if (mInlinePresentation != null) { builder.append(", hasInlinePresentation"); } + if (mInlineTooltipPresentation != null) { + builder.append(", hasInlineTooltipPresentation"); + } if (mAuthentication != null) { builder.append(", hasAuthentication"); } @@ -245,10 +264,12 @@ public final class Dataset implements Parcelable { private ArrayList<AutofillValue> mFieldValues; private ArrayList<RemoteViews> mFieldPresentations; private ArrayList<InlinePresentation> mFieldInlinePresentations; + private ArrayList<InlinePresentation> mFieldInlineTooltipPresentations; private ArrayList<DatasetFieldFilter> mFieldFilters; @Nullable private ClipData mFieldContent; private RemoteViews mPresentation; @Nullable private InlinePresentation mInlinePresentation; + @Nullable private InlinePresentation mInlineTooltipPresentation; private IntentSender mAuthentication; private boolean mDestroyed; @Nullable private String mId; @@ -306,6 +327,31 @@ public final class Dataset implements Parcelable { } /** + * Visualizes this dataset as inline suggestions. + * + * @param inlinePresentation the {@link InlinePresentation} used to visualize this + * dataset as inline suggestions. If the dataset supports inline suggestions this + * should not be null. + * @param inlineTooltipPresentation the {@link InlinePresentation} used to show + * the tooltip for the {@code inlinePresentation}. + * + * @throws IllegalStateException if {@link #build()} was already called. + * + * @return this builder. + */ + public @NonNull Builder setInlinePresentation( + @NonNull InlinePresentation inlinePresentation, + @NonNull InlinePresentation inlineTooltipPresentation) { + throwIfDestroyed(); + Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null"); + Preconditions.checkNotNull(inlineTooltipPresentation, + "inlineTooltipPresentation must be non-null"); + mInlinePresentation = inlinePresentation; + mInlineTooltipPresentation = inlineTooltipPresentation; + return this; + } + + /** * Triggers a custom UI before before autofilling the screen with the contents of this * dataset. * @@ -598,6 +644,41 @@ public final class Dataset implements Parcelable { /** * Sets the value of a field, using a custom {@link RemoteViews presentation} to + * visualize it and an {@link InlinePresentation} to visualize it as an inline suggestion. + * + * @see #setValue(AutofillId, AutofillValue, RemoteViews, InlinePresentation) + * + * @param id id returned by {@link + * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. + * @param value the value to be autofilled. Pass {@code null} if you do not have the value + * but the target view is a logical part of the dataset. For example, if + * the dataset needs authentication and you have no access to the value. + * @param presentation the presentation used to visualize this field. + * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset + * as inline suggestions. If the dataset supports inline suggestions, + * this should not be null. + * @param inlineTooltipPresentation The {@link InlinePresentation} used to show + * the tooltip for the {@code inlinePresentation}. + * + * @throws IllegalStateException if {@link #build()} was already called. + * + * @return this builder. + */ + public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, + @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation, + @NonNull InlinePresentation inlineTooltipPresentation) { + throwIfDestroyed(); + Preconditions.checkNotNull(presentation, "presentation cannot be null"); + Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null"); + Preconditions.checkNotNull(inlineTooltipPresentation, + "inlineTooltipPresentation cannot be null"); + setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, + inlineTooltipPresentation, null); + return this; + } + + /** + * Sets the value of a field, using a custom {@link RemoteViews presentation} to * visualize it and a <a href="#Filtering">explicit filter</a>, and an * {@link InlinePresentation} to visualize it as an inline suggestion. * @@ -641,6 +722,47 @@ public final class Dataset implements Parcelable { } /** + * Sets the value of a field, using a custom {@link RemoteViews presentation} to + * visualize it and a <a href="#Filtering">explicit filter</a>, and an + * {@link InlinePresentation} to visualize it as an inline suggestion. + * + * @see #setValue(AutofillId, AutofillValue, Pattern, RemoteViews, InlinePresentation) + * + * @param id id returned by {@link + * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. + * @param value the value to be autofilled. Pass {@code null} if you do not have the value + * but the target view is a logical part of the dataset. For example, if + * the dataset needs authentication and you have no access to the value. + * @param filter regex used to determine if the dataset should be shown in the autofill UI; + * when {@code null}, it disables filtering on that dataset (this is the recommended + * approach when {@code value} is not {@code null} and field contains sensitive data + * such as passwords). + * @param presentation the presentation used to visualize this field. + * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset + * as inline suggestions. If the dataset supports inline suggestions, this + * should not be null. + * @param inlineTooltipPresentation The {@link InlinePresentation} used to show + * the tooltip for the {@code inlinePresentation}. + * + * @throws IllegalStateException if {@link #build()} was already called. + * + * @return this builder. + */ + public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, + @Nullable Pattern filter, @NonNull RemoteViews presentation, + @NonNull InlinePresentation inlinePresentation, + @NonNull InlinePresentation inlineTooltipPresentation) { + throwIfDestroyed(); + Preconditions.checkNotNull(presentation, "presentation cannot be null"); + Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null"); + Preconditions.checkNotNull(inlineTooltipPresentation, + "inlineTooltipPresentation cannot be null"); + setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, + inlineTooltipPresentation, new DatasetFieldFilter(filter)); + return this; + } + + /** * Sets the value of a field with an <a href="#Filtering">explicit filter</a>, and using an * {@link InlinePresentation} to visualize it as an inline suggestion. * @@ -680,6 +802,15 @@ public final class Dataset implements Parcelable { @Nullable AutofillValue value, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation, @Nullable DatasetFieldFilter filter) { + setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null, + filter); + } + + private void setLifeTheUniverseAndEverything(@NonNull AutofillId id, + @Nullable AutofillValue value, @Nullable RemoteViews presentation, + @Nullable InlinePresentation inlinePresentation, + @Nullable InlinePresentation tooltip, + @Nullable DatasetFieldFilter filter) { Preconditions.checkNotNull(id, "id cannot be null"); if (mFieldIds != null) { final int existingIdx = mFieldIds.indexOf(id); @@ -687,6 +818,7 @@ public final class Dataset implements Parcelable { mFieldValues.set(existingIdx, value); mFieldPresentations.set(existingIdx, presentation); mFieldInlinePresentations.set(existingIdx, inlinePresentation); + mFieldInlineTooltipPresentations.set(existingIdx, tooltip); mFieldFilters.set(existingIdx, filter); return; } @@ -695,12 +827,14 @@ public final class Dataset implements Parcelable { mFieldValues = new ArrayList<>(); mFieldPresentations = new ArrayList<>(); mFieldInlinePresentations = new ArrayList<>(); + mFieldInlineTooltipPresentations = new ArrayList<>(); mFieldFilters = new ArrayList<>(); } mFieldIds.add(id); mFieldValues.add(value); mFieldPresentations.add(presentation); mFieldInlinePresentations.add(inlinePresentation); + mFieldInlineTooltipPresentations.add(tooltip); mFieldFilters.add(filter); } @@ -755,10 +889,12 @@ public final class Dataset implements Parcelable { public void writeToParcel(Parcel parcel, int flags) { parcel.writeParcelable(mPresentation, flags); parcel.writeParcelable(mInlinePresentation, flags); + parcel.writeParcelable(mInlineTooltipPresentation, flags); parcel.writeTypedList(mFieldIds, flags); parcel.writeTypedList(mFieldValues, flags); parcel.writeTypedList(mFieldPresentations, flags); parcel.writeTypedList(mFieldInlinePresentations, flags); + parcel.writeTypedList(mFieldInlineTooltipPresentations, flags); parcel.writeTypedList(mFieldFilters, flags); parcel.writeParcelable(mFieldContent, flags); parcel.writeParcelable(mAuthentication, flags); @@ -770,6 +906,8 @@ public final class Dataset implements Parcelable { public Dataset createFromParcel(Parcel parcel) { final RemoteViews presentation = parcel.readParcelable(null); final InlinePresentation inlinePresentation = parcel.readParcelable(null); + final InlinePresentation inlineTooltipPresentation = + parcel.readParcelable(null); final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR); final ArrayList<AutofillValue> values = @@ -778,6 +916,8 @@ public final class Dataset implements Parcelable { parcel.createTypedArrayList(RemoteViews.CREATOR); final ArrayList<InlinePresentation> inlinePresentations = parcel.createTypedArrayList(InlinePresentation.CREATOR); + final ArrayList<InlinePresentation> inlineTooltipPresentations = + parcel.createTypedArrayList(InlinePresentation.CREATOR); final ArrayList<DatasetFieldFilter> filters = parcel.createTypedArrayList(DatasetFieldFilter.CREATOR); final ClipData fieldContent = parcel.readParcelable(null); @@ -790,8 +930,13 @@ public final class Dataset implements Parcelable { final Builder builder = (presentation != null) ? new Builder(presentation) : new Builder(); if (inlinePresentation != null) { - builder.setInlinePresentation(inlinePresentation); + if (inlineTooltipPresentation != null) { + builder.setInlinePresentation(inlinePresentation, inlineTooltipPresentation); + } else { + builder.setInlinePresentation(inlinePresentation); + } } + if (fieldContent != null) { builder.setContent(ids.get(0), fieldContent); } @@ -802,9 +947,11 @@ public final class Dataset implements Parcelable { final RemoteViews fieldPresentation = presentations.get(i); final InlinePresentation fieldInlinePresentation = i < inlinePresentationsSize ? inlinePresentations.get(i) : null; + final InlinePresentation fieldInlineTooltipPresentation = + i < inlinePresentationsSize ? inlineTooltipPresentations.get(i) : null; final DatasetFieldFilter filter = filters.get(i); builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, - fieldInlinePresentation, filter); + fieldInlinePresentation, fieldInlineTooltipPresentation, filter); } builder.setAuthentication(authentication); builder.setId(datasetId); diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index b1107a8c2efb..740ae260999f 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -23,6 +23,7 @@ import static android.view.autofill.Helper.sDebug; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.Activity; import android.content.IntentSender; @@ -76,6 +77,7 @@ public final class FillResponse implements Parcelable { private final @Nullable Bundle mClientState; private final @Nullable RemoteViews mPresentation; private final @Nullable InlinePresentation mInlinePresentation; + private final @Nullable InlinePresentation mInlineTooltipPresentation; private final @Nullable RemoteViews mHeader; private final @Nullable RemoteViews mFooter; private final @Nullable IntentSender mAuthentication; @@ -95,6 +97,7 @@ public final class FillResponse implements Parcelable { mClientState = builder.mClientState; mPresentation = builder.mPresentation; mInlinePresentation = builder.mInlinePresentation; + mInlineTooltipPresentation = builder.mInlineTooltipPresentation; mHeader = builder.mHeader; mFooter = builder.mFooter; mAuthentication = builder.mAuthentication; @@ -135,6 +138,11 @@ public final class FillResponse implements Parcelable { } /** @hide */ + public @Nullable InlinePresentation getInlineTooltipPresentation() { + return mInlineTooltipPresentation; + } + + /** @hide */ public @Nullable RemoteViews getHeader() { return mHeader; } @@ -219,6 +227,7 @@ public final class FillResponse implements Parcelable { private Bundle mClientState; private RemoteViews mPresentation; private InlinePresentation mInlinePresentation; + private InlinePresentation mInlineTooltipPresentation; private RemoteViews mHeader; private RemoteViews mFooter; private IntentSender mAuthentication; @@ -360,6 +369,22 @@ public final class FillResponse implements Parcelable { public Builder setAuthentication(@NonNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation) { + return setAuthentication(ids, authentication, presentation, inlinePresentation, null); + } + + /** + * Triggers a custom UI before before autofilling the screen with any data set in this + * response. + * + * <p>This method like + * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews, InlinePresentation)} + * but allows setting an {@link InlinePresentation} for the inline suggestion tooltip. + */ + @NonNull + public Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids, + @Nullable IntentSender authentication, @Nullable RemoteViews presentation, + @Nullable InlinePresentation inlinePresentation, + @Nullable InlinePresentation inlineTooltipPresentation) { throwIfDestroyed(); throwIfDisableAutofillCalled(); if (mHeader != null || mFooter != null) { @@ -373,6 +398,7 @@ public final class FillResponse implements Parcelable { mAuthentication = authentication; mPresentation = presentation; mInlinePresentation = inlinePresentation; + mInlineTooltipPresentation = inlineTooltipPresentation; mAuthenticationIds = assertValid(ids); return this; } @@ -737,6 +763,9 @@ public final class FillResponse implements Parcelable { if (mInlinePresentation != null) { builder.append(", hasInlinePresentation"); } + if (mInlineTooltipPresentation != null) { + builder.append(", hasInlineTooltipPresentation"); + } if (mHeader != null) { builder.append(", hasHeader"); } @@ -784,6 +813,7 @@ public final class FillResponse implements Parcelable { parcel.writeParcelable(mAuthentication, flags); parcel.writeParcelable(mPresentation, flags); parcel.writeParcelable(mInlinePresentation, flags); + parcel.writeParcelable(mInlineTooltipPresentation, flags); parcel.writeParcelable(mHeader, flags); parcel.writeParcelable(mFooter, flags); parcel.writeParcelable(mUserData, flags); @@ -818,9 +848,10 @@ public final class FillResponse implements Parcelable { final IntentSender authentication = parcel.readParcelable(null); final RemoteViews presentation = parcel.readParcelable(null); final InlinePresentation inlinePresentation = parcel.readParcelable(null); + final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null); if (authenticationIds != null) { builder.setAuthentication(authenticationIds, authentication, presentation, - inlinePresentation); + inlinePresentation, inlineTooltipPresentation); } final RemoteViews header = parcel.readParcelable(null); if (header != null) { diff --git a/core/java/android/service/autofill/InlinePresentation.java b/core/java/android/service/autofill/InlinePresentation.java index fbf23b69addf..40349576c460 100644 --- a/core/java/android/service/autofill/InlinePresentation.java +++ b/core/java/android/service/autofill/InlinePresentation.java @@ -75,9 +75,23 @@ public final class InlinePresentation implements Parcelable { return hints.toArray(new String[hints.size()]); } + /** + * Creates a presentation for the inline suggestion tooltip + * + * @param slice Represents the UI content and the action for the inline suggestion tooltip. + * @param spec Specifies the UI specification for the inline suggestion tooltip. + * @return An {@link InlinePresentation} for the inline suggestion tooltip + */ + @NonNull + public static InlinePresentation createTooltipPresentation(@NonNull Slice slice, + @NonNull InlinePresentationSpec spec) { + return new InlinePresentation(slice, spec, /* pinned */ false); + + } + - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.22. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -259,10 +273,10 @@ public final class InlinePresentation implements Parcelable { }; @DataClass.Generated( - time = 1604456277638L, - codegenVersion = "1.0.20", + time = 1615968415006L, + codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/service/autofill/InlinePresentation.java", - inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final boolean mPinned\npublic @android.annotation.NonNull @android.annotation.Size java.lang.String[] getAutofillHints()\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)") + inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final boolean mPinned\npublic @android.annotation.NonNull @android.annotation.Size java.lang.String[] getAutofillHints()\npublic static @android.annotation.NonNull android.service.autofill.InlinePresentation createTooltipPresentation(android.app.slice.Slice,android.widget.inline.InlinePresentationSpec)\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 048d9f57aded..9ba39a1b37f7 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -353,6 +353,7 @@ public class VoiceInteractionService extends Service { * @hide */ @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) @NonNull public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java index a0875a275ec6..d0000005c237 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -1353,10 +1353,7 @@ public class TelephonyCallback { /** * Interface for current physical channel configuration listener. - * - * @hide */ - @SystemApi public interface PhysicalChannelConfigListener { /** * Callback invoked when the current physical channel configuration has changed diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java index b8893cee834d..27c637bb59dd 100644 --- a/core/java/android/view/inputmethod/InlineSuggestion.java +++ b/core/java/android/view/inputmethod/InlineSuggestion.java @@ -31,6 +31,7 @@ import android.os.RemoteException; import android.util.Size; import android.util.Slog; import android.view.SurfaceControlViewHost; +import android.view.View; import android.view.ViewGroup; import android.widget.inline.InlineContentView; @@ -38,6 +39,7 @@ import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; import com.android.internal.view.inline.IInlineContentCallback; import com.android.internal.view.inline.IInlineContentProvider; +import com.android.internal.view.inline.InlineTooltipUi; import java.lang.ref.WeakReference; import java.util.concurrent.Executor; @@ -75,6 +77,15 @@ public final class InlineSuggestion implements Parcelable { private InlineContentCallbackImpl mInlineContentCallback; /** + * Used to show up the inline suggestion tooltip. + * + * @hide + */ + @Nullable + @DataClass.ParcelWith(InlineTooltipUiParceling.class) + private InlineTooltipUi mInlineTooltipUi; + + /** * Creates a new {@link InlineSuggestion}, for testing purpose. * * @hide @@ -82,7 +93,8 @@ public final class InlineSuggestion implements Parcelable { @TestApi @NonNull public static InlineSuggestion newInlineSuggestion(@NonNull InlineSuggestionInfo info) { - return new InlineSuggestion(info, null, /* inlineContentCallback */ null); + return new InlineSuggestion(info, null, /* inlineContentCallback */ null, + /* inlineTooltipUi */ null); } /** @@ -92,7 +104,7 @@ public final class InlineSuggestion implements Parcelable { */ public InlineSuggestion(@NonNull InlineSuggestionInfo info, @Nullable IInlineContentProvider contentProvider) { - this(info, contentProvider, /* inlineContentCallback */ null); + this(info, contentProvider, /* inlineContentCallback */ null, /* inlineTooltipUi */ null); } /** @@ -136,9 +148,21 @@ public final class InlineSuggestion implements Parcelable { "size is neither between min:" + minSize + " and max:" + maxSize + ", nor wrap_content"); } - mInlineContentCallback = getInlineContentCallback(context, callbackExecutor, callback); + + InlineSuggestion toolTip = mInfo.getTooltip(); + if (toolTip != null) { + if (mInlineTooltipUi == null) { + mInlineTooltipUi = new InlineTooltipUi(context); + } + } else { + mInlineTooltipUi = null; + } + + mInlineContentCallback = getInlineContentCallback(context, callbackExecutor, callback, + mInlineTooltipUi); if (mContentProvider == null) { callbackExecutor.execute(() -> callback.accept(/* view */ null)); + mInlineTooltipUi = null; return; } try { @@ -148,6 +172,13 @@ public final class InlineSuggestion implements Parcelable { Slog.w(TAG, "Error creating suggestion content surface: " + e); callbackExecutor.execute(() -> callback.accept(/* view */ null)); } + if (toolTip == null) return; + + final Size tooltipSize = new Size(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + mInfo.getTooltip().inflate(context, tooltipSize, callbackExecutor, view -> { + Handler.getMain().post(() -> mInlineTooltipUi.setTooltipView(view)); + }); } /** @@ -162,12 +193,13 @@ public final class InlineSuggestion implements Parcelable { } private synchronized InlineContentCallbackImpl getInlineContentCallback(Context context, - Executor callbackExecutor, Consumer<InlineContentView> callback) { + Executor callbackExecutor, Consumer<InlineContentView> callback, + InlineTooltipUi inlineTooltipUi) { if (mInlineContentCallback != null) { throw new IllegalStateException("Already called #inflate()"); } return new InlineContentCallbackImpl(context, mContentProvider, callbackExecutor, - callback); + callback, inlineTooltipUi); } /** @@ -267,14 +299,19 @@ public final class InlineSuggestion implements Parcelable { @Nullable private Consumer<SurfaceControlViewHost.SurfacePackage> mSurfacePackageConsumer; + @Nullable + private InlineTooltipUi mInlineTooltipUi; + InlineContentCallbackImpl(@NonNull Context context, @Nullable IInlineContentProvider inlineContentProvider, @NonNull @CallbackExecutor Executor callbackExecutor, - @NonNull Consumer<InlineContentView> callback) { + @NonNull Consumer<InlineContentView> callback, + @Nullable InlineTooltipUi inlineTooltipUi) { mContext = context; mInlineContentProvider = inlineContentProvider; mCallbackExecutor = callbackExecutor; mCallback = callback; + mInlineTooltipUi = inlineTooltipUi; } @BinderThread @@ -305,6 +342,17 @@ public final class InlineSuggestion implements Parcelable { mCallbackExecutor.execute(() -> mCallback.accept(/* view */null)); } else { mView = new InlineContentView(mContext); + if (mInlineTooltipUi != null) { + mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, + int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + if (mInlineTooltipUi != null) { + mInlineTooltipUi.update(mView); + } + } + }); + } mView.setLayoutParams(new ViewGroup.LayoutParams(width, height)); mView.setChildSurfacePackageUpdater(getSurfacePackageUpdater()); mCallbackExecutor.execute(() -> mCallback.accept(mView)); @@ -425,10 +473,25 @@ public final class InlineSuggestion implements Parcelable { } } + /** + * This class used to provide parcelling logic for InlineContentCallbackImpl. It's intended to + * make this parcelling a no-op, since it can't be parceled and we don't need to parcel it. + */ + private static class InlineTooltipUiParceling implements + Parcelling<InlineTooltipUi> { + @Override + public void parcel(InlineTooltipUi item, Parcel dest, int parcelFlags) { + } + + @Override + public InlineTooltipUi unparcel(Parcel source) { + return null; + } + } - // Code below generated by codegen v1.0.15. + // Code below generated by codegen v1.0.22. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -446,18 +509,22 @@ public final class InlineSuggestion implements Parcelable { * * @param inlineContentCallback * Used to keep a strong reference to the callback so it doesn't get garbage collected. + * @param inlineTooltipUi + * Used to show up the inline suggestion tooltip. * @hide */ @DataClass.Generated.Member public InlineSuggestion( @NonNull InlineSuggestionInfo info, @Nullable IInlineContentProvider contentProvider, - @Nullable InlineContentCallbackImpl inlineContentCallback) { + @Nullable InlineContentCallbackImpl inlineContentCallback, + @Nullable InlineTooltipUi inlineTooltipUi) { this.mInfo = info; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mInfo); this.mContentProvider = contentProvider; this.mInlineContentCallback = inlineContentCallback; + this.mInlineTooltipUi = inlineTooltipUi; // onConstructed(); // You can define this method to get a callback } @@ -485,6 +552,16 @@ public final class InlineSuggestion implements Parcelable { return mInlineContentCallback; } + /** + * Used to show up the inline suggestion tooltip. + * + * @hide + */ + @DataClass.Generated.Member + public @Nullable InlineTooltipUi getInlineTooltipUi() { + return mInlineTooltipUi; + } + @Override @DataClass.Generated.Member public String toString() { @@ -494,7 +571,8 @@ public final class InlineSuggestion implements Parcelable { return "InlineSuggestion { " + "info = " + mInfo + ", " + "contentProvider = " + mContentProvider + ", " + - "inlineContentCallback = " + mInlineContentCallback + + "inlineContentCallback = " + mInlineContentCallback + ", " + + "inlineTooltipUi = " + mInlineTooltipUi + " }"; } @@ -513,7 +591,8 @@ public final class InlineSuggestion implements Parcelable { return true && java.util.Objects.equals(mInfo, that.mInfo) && java.util.Objects.equals(mContentProvider, that.mContentProvider) - && java.util.Objects.equals(mInlineContentCallback, that.mInlineContentCallback); + && java.util.Objects.equals(mInlineContentCallback, that.mInlineContentCallback) + && java.util.Objects.equals(mInlineTooltipUi, that.mInlineTooltipUi); } @Override @@ -526,6 +605,7 @@ public final class InlineSuggestion implements Parcelable { _hash = 31 * _hash + java.util.Objects.hashCode(mInfo); _hash = 31 * _hash + java.util.Objects.hashCode(mContentProvider); _hash = 31 * _hash + java.util.Objects.hashCode(mInlineContentCallback); + _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipUi); return _hash; } @@ -540,6 +620,17 @@ public final class InlineSuggestion implements Parcelable { } } + @DataClass.Generated.Member + static Parcelling<InlineTooltipUi> sParcellingForInlineTooltipUi = + Parcelling.Cache.get( + InlineTooltipUiParceling.class); + static { + if (sParcellingForInlineTooltipUi == null) { + sParcellingForInlineTooltipUi = Parcelling.Cache.put( + new InlineTooltipUiParceling()); + } + } + @Override @DataClass.Generated.Member public void writeToParcel(@NonNull Parcel dest, int flags) { @@ -549,10 +640,12 @@ public final class InlineSuggestion implements Parcelable { byte flg = 0; if (mContentProvider != null) flg |= 0x2; if (mInlineContentCallback != null) flg |= 0x4; + if (mInlineTooltipUi != null) flg |= 0x8; dest.writeByte(flg); dest.writeTypedObject(mInfo, flags); if (mContentProvider != null) dest.writeStrongInterface(mContentProvider); sParcellingForInlineContentCallback.parcel(mInlineContentCallback, dest, flags); + sParcellingForInlineTooltipUi.parcel(mInlineTooltipUi, dest, flags); } @Override @@ -570,12 +663,14 @@ public final class InlineSuggestion implements Parcelable { InlineSuggestionInfo info = (InlineSuggestionInfo) in.readTypedObject(InlineSuggestionInfo.CREATOR); IInlineContentProvider contentProvider = (flg & 0x2) == 0 ? null : IInlineContentProvider.Stub.asInterface(in.readStrongBinder()); InlineContentCallbackImpl inlineContentCallback = sParcellingForInlineContentCallback.unparcel(in); + InlineTooltipUi inlineTooltipUi = sParcellingForInlineTooltipUi.unparcel(in); this.mInfo = info; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mInfo); this.mContentProvider = contentProvider; this.mInlineContentCallback = inlineContentCallback; + this.mInlineTooltipUi = inlineTooltipUi; // onConstructed(); // You can define this method to get a callback } @@ -595,10 +690,10 @@ public final class InlineSuggestion implements Parcelable { }; @DataClass.Generated( - time = 1589396017700L, - codegenVersion = "1.0.15", + time = 1615562097666L, + codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java", - inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\nprivate @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineContentCallbackImplParceling.class) @android.annotation.Nullable android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl mInlineContentCallback\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nprivate static boolean isValid(int,int,int)\nprivate synchronized android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") + inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\nprivate @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineContentCallbackImplParceling.class) @android.annotation.Nullable android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl mInlineContentCallback\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineTooltipUiParceling.class) com.android.internal.view.inline.InlineTooltipUi mInlineTooltipUi\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nprivate static boolean isValid(int,int,int)\nprivate synchronized android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>,com.android.internal.view.inline.InlineTooltipUi)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/inputmethod/InlineSuggestionInfo.java b/core/java/android/view/inputmethod/InlineSuggestionInfo.java index 10fd0e036814..5798614b8c9f 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionInfo.java +++ b/core/java/android/view/inputmethod/InlineSuggestionInfo.java @@ -73,6 +73,11 @@ public final class InlineSuggestionInfo implements Parcelable { private final boolean mPinned; /** + * @hide + */ + private final @Nullable InlineSuggestion mTooltip; + + /** * Creates a new {@link InlineSuggestionInfo}, for testing purpose. * * @hide @@ -84,12 +89,30 @@ public final class InlineSuggestionInfo implements Parcelable { @NonNull @Source String source, @SuppressLint("NullableCollection") @Nullable String[] autofillHints, @NonNull @Type String type, boolean isPinned) { - return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned); + return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned, + null); + } + + /** + * Creates a new {@link InlineSuggestionInfo}, for testing purpose. + * + * @hide + */ + @NonNull + public static InlineSuggestionInfo newInlineSuggestionInfo( + @NonNull InlinePresentationSpec presentationSpec, + @NonNull @Source String source, + @Nullable String[] autofillHints, @NonNull @Type String type, boolean isPinned, + @Nullable InlineSuggestion tooltip) { + return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned, + tooltip); } - // Code below generated by codegen v1.0.20. + + + // Code below generated by codegen v1.0.22. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -141,7 +164,8 @@ public final class InlineSuggestionInfo implements Parcelable { @NonNull @Source String source, @Nullable String[] autofillHints, @NonNull @Type String type, - boolean pinned) { + boolean pinned, + @Nullable InlineSuggestion tooltip) { this.mInlinePresentationSpec = inlinePresentationSpec; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mInlinePresentationSpec); @@ -171,6 +195,7 @@ public final class InlineSuggestionInfo implements Parcelable { com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mType); this.mPinned = pinned; + this.mTooltip = tooltip; // onConstructed(); // You can define this method to get a callback } @@ -215,6 +240,14 @@ public final class InlineSuggestionInfo implements Parcelable { return mPinned; } + /** + * @hide + */ + @DataClass.Generated.Member + public @Nullable InlineSuggestion getTooltip() { + return mTooltip; + } + @Override @DataClass.Generated.Member public String toString() { @@ -226,7 +259,8 @@ public final class InlineSuggestionInfo implements Parcelable { "source = " + mSource + ", " + "autofillHints = " + java.util.Arrays.toString(mAutofillHints) + ", " + "type = " + mType + ", " + - "pinned = " + mPinned + + "pinned = " + mPinned + ", " + + "tooltip = " + mTooltip + " }"; } @@ -247,7 +281,8 @@ public final class InlineSuggestionInfo implements Parcelable { && java.util.Objects.equals(mSource, that.mSource) && java.util.Arrays.equals(mAutofillHints, that.mAutofillHints) && java.util.Objects.equals(mType, that.mType) - && mPinned == that.mPinned; + && mPinned == that.mPinned + && java.util.Objects.equals(mTooltip, that.mTooltip); } @Override @@ -262,6 +297,7 @@ public final class InlineSuggestionInfo implements Parcelable { _hash = 31 * _hash + java.util.Arrays.hashCode(mAutofillHints); _hash = 31 * _hash + java.util.Objects.hashCode(mType); _hash = 31 * _hash + Boolean.hashCode(mPinned); + _hash = 31 * _hash + java.util.Objects.hashCode(mTooltip); return _hash; } @@ -274,11 +310,13 @@ public final class InlineSuggestionInfo implements Parcelable { byte flg = 0; if (mPinned) flg |= 0x10; if (mAutofillHints != null) flg |= 0x4; + if (mTooltip != null) flg |= 0x20; dest.writeByte(flg); dest.writeTypedObject(mInlinePresentationSpec, flags); dest.writeString(mSource); if (mAutofillHints != null) dest.writeStringArray(mAutofillHints); dest.writeString(mType); + if (mTooltip != null) dest.writeTypedObject(mTooltip, flags); } @Override @@ -298,6 +336,7 @@ public final class InlineSuggestionInfo implements Parcelable { String source = in.readString(); String[] autofillHints = (flg & 0x4) == 0 ? null : in.createStringArray(); String type = in.readString(); + InlineSuggestion tooltip = (flg & 0x20) == 0 ? null : (InlineSuggestion) in.readTypedObject(InlineSuggestion.CREATOR); this.mInlinePresentationSpec = inlinePresentationSpec; com.android.internal.util.AnnotationValidations.validate( @@ -328,6 +367,7 @@ public final class InlineSuggestionInfo implements Parcelable { com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mType); this.mPinned = pinned; + this.mTooltip = tooltip; // onConstructed(); // You can define this method to get a callback } @@ -347,10 +387,10 @@ public final class InlineSuggestionInfo implements Parcelable { }; @DataClass.Generated( - time = 1604456249219L, - codegenVersion = "1.0.20", + time = 1614287616672L, + codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionInfo.java", - inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_SUGGESTION\npublic static final @android.annotation.SuppressLint @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_ACTION\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String mType\nprivate final boolean mPinned\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.widget.inline.InlinePresentationSpec,java.lang.String,java.lang.String[],java.lang.String,boolean)\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") + inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_SUGGESTION\npublic static final @android.annotation.SuppressLint @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_ACTION\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String mType\nprivate final boolean mPinned\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestion mTooltip\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.widget.inline.InlinePresentationSpec,java.lang.String,java.lang.String[],java.lang.String,boolean)\npublic static @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.widget.inline.InlinePresentationSpec,java.lang.String,java.lang.String[],java.lang.String,boolean,android.view.inputmethod.InlineSuggestion)\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java index 0ab4e05227ba..e1e175512edc 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java +++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java @@ -106,6 +106,27 @@ public final class InlineSuggestionsRequest implements Parcelable { private int mHostDisplayId; /** + * Specifies the UI specification for the inline suggestion tooltip in the response. + */ + private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec; + + /** + * Whether the IME supports inline suggestions from the default Autofill service that + * provides the input view. + * + * Note: The default value is {@code true}. + */ + private boolean mServiceSupported; + + /** + * Whether the IME supports inline suggestions from the application that provides the + * input view. + * + * Note: The default value is {@code true}. + */ + private boolean mClientSupported; + + /** * @hide * @see {@link #mHostInputToken}. */ @@ -151,6 +172,10 @@ public final class InlineSuggestionsRequest implements Parcelable { for (int i = 0; i < mInlinePresentationSpecs.size(); i++) { mInlinePresentationSpecs.get(i).filterContentTypes(); } + + if (mInlineTooltipPresentationSpec != null) { + mInlineTooltipPresentationSpec.filterContentTypes(); + } } private static int defaultMaxSuggestionCount() { @@ -161,6 +186,10 @@ public final class InlineSuggestionsRequest implements Parcelable { return ActivityThread.currentPackageName(); } + private static InlinePresentationSpec defaultInlineTooltipPresentationSpec() { + return null; + } + /** * The {@link InlineSuggestionsRequest#getSupportedLocales()} now returns empty locale list when * it's not set, instead of the default system locale. @@ -191,6 +220,14 @@ public final class InlineSuggestionsRequest implements Parcelable { return Bundle.EMPTY; } + private static boolean defaultServiceSupported() { + return true; + } + + private static boolean defaultClientSupported() { + return true; + } + /** @hide */ abstract static class BaseBuilder { abstract Builder setInlinePresentationSpecs( @@ -203,6 +240,16 @@ public final class InlineSuggestionsRequest implements Parcelable { abstract Builder setHostDisplayId(int value); } + /** @hide */ + public boolean isServiceSupported() { + return mServiceSupported; + } + + /** @hide */ + public boolean isClientSupported() { + return mClientSupported; + } + // Code below generated by codegen v1.0.22. @@ -226,7 +273,10 @@ public final class InlineSuggestionsRequest implements Parcelable { @NonNull LocaleList supportedLocales, @NonNull Bundle extras, @Nullable IBinder hostInputToken, - int hostDisplayId) { + int hostDisplayId, + @Nullable InlinePresentationSpec inlineTooltipPresentationSpec, + boolean serviceSupported, + boolean clientSupported) { this.mMaxSuggestionCount = maxSuggestionCount; this.mInlinePresentationSpecs = inlinePresentationSpecs; com.android.internal.util.AnnotationValidations.validate( @@ -242,6 +292,9 @@ public final class InlineSuggestionsRequest implements Parcelable { NonNull.class, null, mExtras); this.mHostInputToken = hostInputToken; this.mHostDisplayId = hostDisplayId; + this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec; + this.mServiceSupported = serviceSupported; + this.mClientSupported = clientSupported; onConstructed(); } @@ -324,6 +377,16 @@ public final class InlineSuggestionsRequest implements Parcelable { return mHostDisplayId; } + /** + * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response. + * + * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec) + */ + @DataClass.Generated.Member + public @Nullable InlinePresentationSpec getInlineTooltipPresentationSpec() { + return mInlineTooltipPresentationSpec; + } + @Override @DataClass.Generated.Member public String toString() { @@ -337,7 +400,10 @@ public final class InlineSuggestionsRequest implements Parcelable { "supportedLocales = " + mSupportedLocales + ", " + "extras = " + mExtras + ", " + "hostInputToken = " + mHostInputToken + ", " + - "hostDisplayId = " + mHostDisplayId + + "hostDisplayId = " + mHostDisplayId + ", " + + "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + ", " + + "serviceSupported = " + mServiceSupported + ", " + + "clientSupported = " + mClientSupported + " }"; } @@ -360,7 +426,10 @@ public final class InlineSuggestionsRequest implements Parcelable { && java.util.Objects.equals(mSupportedLocales, that.mSupportedLocales) && extrasEquals(that.mExtras) && java.util.Objects.equals(mHostInputToken, that.mHostInputToken) - && mHostDisplayId == that.mHostDisplayId; + && mHostDisplayId == that.mHostDisplayId + && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec) + && mServiceSupported == that.mServiceSupported + && mClientSupported == that.mClientSupported; } @Override @@ -377,6 +446,9 @@ public final class InlineSuggestionsRequest implements Parcelable { _hash = 31 * _hash + java.util.Objects.hashCode(mExtras); _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken); _hash = 31 * _hash + mHostDisplayId; + _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipPresentationSpec); + _hash = 31 * _hash + Boolean.hashCode(mServiceSupported); + _hash = 31 * _hash + Boolean.hashCode(mClientSupported); return _hash; } @@ -386,9 +458,12 @@ public final class InlineSuggestionsRequest implements Parcelable { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } - byte flg = 0; + int flg = 0; + if (mServiceSupported) flg |= 0x100; + if (mClientSupported) flg |= 0x200; if (mHostInputToken != null) flg |= 0x20; - dest.writeByte(flg); + if (mInlineTooltipPresentationSpec != null) flg |= 0x80; + dest.writeInt(flg); dest.writeInt(mMaxSuggestionCount); dest.writeParcelableList(mInlinePresentationSpecs, flags); dest.writeString(mHostPackageName); @@ -396,6 +471,7 @@ public final class InlineSuggestionsRequest implements Parcelable { dest.writeBundle(mExtras); parcelHostInputToken(dest, flags); dest.writeInt(mHostDisplayId); + if (mInlineTooltipPresentationSpec != null) dest.writeTypedObject(mInlineTooltipPresentationSpec, flags); } @Override @@ -409,7 +485,9 @@ public final class InlineSuggestionsRequest implements Parcelable { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } - byte flg = in.readByte(); + int flg = in.readInt(); + boolean serviceSupported = (flg & 0x100) != 0; + boolean clientSupported = (flg & 0x200) != 0; int maxSuggestionCount = in.readInt(); List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>(); in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader()); @@ -418,6 +496,7 @@ public final class InlineSuggestionsRequest implements Parcelable { Bundle extras = in.readBundle(); IBinder hostInputToken = unparcelHostInputToken(in); int hostDisplayId = in.readInt(); + InlinePresentationSpec inlineTooltipPresentationSpec = (flg & 0x80) == 0 ? null : (InlinePresentationSpec) in.readTypedObject(InlinePresentationSpec.CREATOR); this.mMaxSuggestionCount = maxSuggestionCount; this.mInlinePresentationSpecs = inlinePresentationSpecs; @@ -434,6 +513,9 @@ public final class InlineSuggestionsRequest implements Parcelable { NonNull.class, null, mExtras); this.mHostInputToken = hostInputToken; this.mHostDisplayId = hostDisplayId; + this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec; + this.mServiceSupported = serviceSupported; + this.mClientSupported = clientSupported; onConstructed(); } @@ -466,6 +548,9 @@ public final class InlineSuggestionsRequest implements Parcelable { private @NonNull Bundle mExtras; private @Nullable IBinder mHostInputToken; private int mHostDisplayId; + private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec; + private boolean mServiceSupported; + private boolean mClientSupported; private long mBuilderFieldsSet = 0L; @@ -597,10 +682,51 @@ public final class InlineSuggestionsRequest implements Parcelable { return this; } + /** + * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response. + * + * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)s + */ + @DataClass.Generated.Member + public @NonNull Builder setInlineTooltipPresentationSpec(@NonNull InlinePresentationSpec value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x80; + mInlineTooltipPresentationSpec = value; + return this; + } + + /** + * Whether the IME supports inline suggestions from the default Autofill service that + * provides the input view. + * + * Note: The default value is {@code true}. + */ + @DataClass.Generated.Member + public @NonNull Builder setServiceSupported(boolean value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x100; + mServiceSupported = value; + return this; + } + + /** + * Whether the IME supports inline suggestions from the application that provides the + * input view. + * + * Note: The default value is {@code true}. + */ + @DataClass.Generated.Member + public @NonNull Builder setClientSupported(boolean value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x200; + mClientSupported = value; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull InlineSuggestionsRequest build() { checkNotUsed(); - mBuilderFieldsSet |= 0x80; // Mark builder used + mBuilderFieldsSet |= 0x400; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mMaxSuggestionCount = defaultMaxSuggestionCount(); @@ -620,6 +746,15 @@ public final class InlineSuggestionsRequest implements Parcelable { if ((mBuilderFieldsSet & 0x40) == 0) { mHostDisplayId = defaultHostDisplayId(); } + if ((mBuilderFieldsSet & 0x80) == 0) { + mInlineTooltipPresentationSpec = defaultInlineTooltipPresentationSpec(); + } + if ((mBuilderFieldsSet & 0x100) == 0) { + mServiceSupported = defaultServiceSupported(); + } + if ((mBuilderFieldsSet & 0x200) == 0) { + mClientSupported = defaultClientSupported(); + } InlineSuggestionsRequest o = new InlineSuggestionsRequest( mMaxSuggestionCount, mInlinePresentationSpecs, @@ -627,12 +762,15 @@ public final class InlineSuggestionsRequest implements Parcelable { mSupportedLocales, mExtras, mHostInputToken, - mHostDisplayId); + mHostDisplayId, + mInlineTooltipPresentationSpec, + mServiceSupported, + mClientSupported); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x80) != 0) { + if ((mBuilderFieldsSet & 0x400) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -640,10 +778,10 @@ public final class InlineSuggestionsRequest implements Parcelable { } @DataClass.Generated( - time = 1612206506050L, + time = 1615798784918L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java", - inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\npublic void filterContentTypes()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []") + inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate boolean mServiceSupported\nprivate boolean mClientSupported\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\npublic void filterContentTypes()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nprivate static boolean defaultServiceSupported()\nprivate static boolean defaultClientSupported()\npublic boolean isServiceSupported()\npublic boolean isClientSupported()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl index 7f6c4b474d3a..d347f31eb934 100644 --- a/core/java/android/view/translation/ITranslationManager.aidl +++ b/core/java/android/view/translation/ITranslationManager.aidl @@ -17,6 +17,7 @@ package android.view.translation; import android.os.IBinder; +import android.os.IRemoteCallback; import android.view.autofill.AutofillId; import android.view.translation.TranslationSpec; import com.android.internal.os.IResultReceiver; @@ -40,4 +41,7 @@ oneway interface ITranslationManager { void updateUiTranslationStateByTaskId(int state, in TranslationSpec sourceSpec, in TranslationSpec destSpec, in List<AutofillId> viewIds, int taskId, int userId); + + void registerUiTranslationStateCallback(in IRemoteCallback callback, int userId); + void unregisterUiTranslationStateCallback(in IRemoteCallback callback, int userId); } diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java index 7c73e701b7c8..852ffe8303b1 100644 --- a/core/java/android/view/translation/UiTranslationManager.java +++ b/core/java/android/view/translation/UiTranslationManager.java @@ -16,28 +16,36 @@ package android.view.translation; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.app.assist.ActivityId; import android.content.Context; +import android.os.Binder; +import android.os.Bundle; +import android.os.IRemoteCallback; import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; import android.view.View; import android.view.autofill.AutofillId; +import com.android.internal.annotations.GuardedBy; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.Executor; +// TODO(b/178044703): Describe what UI Translation is. /** * The {@link UiTranslationManager} class provides ways for apps to use the ui translation * function in framework. - * - * @hide */ -@SystemApi public final class UiTranslationManager { private static final String TAG = "UiTranslationManager"; @@ -88,6 +96,14 @@ public final class UiTranslationManager { public @interface UiTranslationState { } + // Keys for the data transmitted in the internal UI Translation state callback. + /** @hide */ + public static final String EXTRA_STATE = "state"; + /** @hide */ + public static final String EXTRA_SOURCE_LOCALE = "source_locale"; + /** @hide */ + public static final String EXTRA_TARGET_LOCALE = "target_locale"; + @NonNull private final Context mContext; @@ -111,9 +127,12 @@ public final class UiTranslationManager { * @param destSpec {@link TranslationSpec} for the translated data. * @param viewIds A list of the {@link View}'s {@link AutofillId} which needs to be translated * @param taskId the Activity Task id which needs ui translation + * + * @hide */ // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void startTranslation(@NonNull TranslationSpec sourceSpec, @NonNull TranslationSpec destSpec, @NonNull List<AutofillId> viewIds, int taskId) { @@ -141,8 +160,11 @@ public final class UiTranslationManager { * @throws IllegalArgumentException if the no {@link View}'s {@link AutofillId} in the list * @throws NullPointerException the sourceSpec, destSpec, viewIds, activityId or * {@link android.app.assist.ActivityId#getToken()} is {@code null} + * + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void startTranslation(@NonNull TranslationSpec sourceSpec, @NonNull TranslationSpec destSpec, @NonNull List<AutofillId> viewIds, @NonNull ActivityId activityId) { @@ -171,9 +193,12 @@ public final class UiTranslationManager { * NOTE: Please use {@code finishTranslation(ActivityId)} instead. * * @param taskId the Activity Task id which needs ui translation + * + * @hide */ // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void finishTranslation(int taskId) { try { mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_FINISHED, @@ -191,8 +216,11 @@ public final class UiTranslationManager { * @param activityId the identifier for the Activity which needs ui translation * @throws NullPointerException the activityId or * {@link android.app.assist.ActivityId#getToken()} is {@code null} + * + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void finishTranslation(@NonNull ActivityId activityId) { try { Objects.requireNonNull(activityId); @@ -212,9 +240,12 @@ public final class UiTranslationManager { * NOTE: Please use {@code pauseTranslation(ActivityId)} instead. * * @param taskId the Activity Task id which needs ui translation + * + * @hide */ // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void pauseTranslation(int taskId) { try { mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_PAUSED, @@ -232,8 +263,11 @@ public final class UiTranslationManager { * @param activityId the identifier for the Activity which needs ui translation * @throws NullPointerException the activityId or * {@link android.app.assist.ActivityId#getToken()} is {@code null} + * + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void pauseTranslation(@NonNull ActivityId activityId) { try { Objects.requireNonNull(activityId); @@ -253,9 +287,12 @@ public final class UiTranslationManager { * NOTE: Please use {@code resumeTranslation(ActivityId)} instead. * * @param taskId the Activity Task id which needs ui translation + * + * @hide */ // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void resumeTranslation(int taskId) { try { mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_RESUMED, @@ -273,8 +310,11 @@ public final class UiTranslationManager { * @param activityId the identifier for the Activity which needs ui translation * @throws NullPointerException the activityId or * {@link android.app.assist.ActivityId#getToken()} is {@code null} + * + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void resumeTranslation(@NonNull ActivityId activityId) { try { Objects.requireNonNull(activityId); @@ -286,4 +326,105 @@ public final class UiTranslationManager { throw e.rethrowFromSystemServer(); } } + + // TODO(b/178044703): Fix the View API link when it becomes public. + /** + * Register for notifications of UI Translation state changes on the foreground activity. This + * is available to the owning application itself and also the current input method. + * <p> + * The application whose UI is being translated can use this to customize the UI Translation + * behavior in ways that aren't made easy by methods like + * View#onCreateTranslationRequest(). + * <p> + * Input methods can use this to offer complementary features to UI Translation; for example, + * enabling outgoing message translation when the system is translating incoming messages in a + * communication app. + * + * @param callback the callback to register for receiving the state change + * notifications + */ + public void registerUiTranslationStateCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull UiTranslationStateCallback callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + synchronized (mCallbacks) { + if (mCallbacks.containsKey(callback)) { + Log.w(TAG, "registerUiTranslationStateCallback: callback already registered;" + + " ignoring."); + return; + } + final IRemoteCallback remoteCallback = + new UiTranslationStateRemoteCallback(executor, callback); + try { + mService.registerUiTranslationStateCallback(remoteCallback, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mCallbacks.put(callback, remoteCallback); + } + } + + /** + * Unregister {@code callback}. + * + * @see #registerUiTranslationStateCallback(Executor, UiTranslationStateCallback) + */ + public void unregisterUiTranslationStateCallback(@NonNull UiTranslationStateCallback callback) { + Objects.requireNonNull(callback); + + synchronized (mCallbacks) { + final IRemoteCallback remoteCallback = mCallbacks.get(callback); + if (remoteCallback == null) { + Log.w(TAG, "unregisterUiTranslationStateCallback: callback not found; ignoring."); + return; + } + try { + mService.unregisterUiTranslationStateCallback(remoteCallback, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mCallbacks.remove(callback); + } + } + + @NonNull + @GuardedBy("mCallbacks") + private final Map<UiTranslationStateCallback, IRemoteCallback> mCallbacks = new ArrayMap<>(); + + private static class UiTranslationStateRemoteCallback extends IRemoteCallback.Stub { + private final Executor mExecutor; + private final UiTranslationStateCallback mCallback; + + UiTranslationStateRemoteCallback(Executor executor, + UiTranslationStateCallback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void sendResult(Bundle bundle) { + Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> onStateChange(bundle))); + } + + private void onStateChange(Bundle bundle) { + int state = bundle.getInt(EXTRA_STATE); + switch (state) { + case STATE_UI_TRANSLATION_STARTED: + case STATE_UI_TRANSLATION_RESUMED: + mCallback.onStarted( + bundle.getString(EXTRA_SOURCE_LOCALE), + bundle.getString(EXTRA_TARGET_LOCALE)); + break; + case STATE_UI_TRANSLATION_PAUSED: + mCallback.onPaused(); + break; + case STATE_UI_TRANSLATION_FINISHED: + mCallback.onFinished(); + break; + default: + Log.wtf(TAG, "Unexpected translation state:" + state); + } + } + } } diff --git a/core/java/android/view/translation/UiTranslationStateCallback.java b/core/java/android/view/translation/UiTranslationStateCallback.java new file mode 100644 index 000000000000..1946b703935d --- /dev/null +++ b/core/java/android/view/translation/UiTranslationStateCallback.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.translation; + +import android.annotation.NonNull; + +import java.util.concurrent.Executor; + +/** + * Callback for listening to UI Translation state changes. See {@link + * UiTranslationManager#registerUiTranslationStateCallback(Executor, UiTranslationStateCallback)}. + */ +public interface UiTranslationStateCallback { + + /** + * The system is requesting translation of the UI from {@code sourceLocale} to {@code + * targetLocale}. + * <p> + * This is also called if either the requested {@code sourceLocale} or {@code targetLocale} has + * changed; or called again after {@link #onPaused()}. + */ + void onStarted(@NonNull String sourceLocale, @NonNull String targetLocale); + + /** + * The system is requesting that the application temporarily show the UI contents in their + * original language. + */ + void onPaused(); + + /** + * The UI Translation session has ended. + */ + void onFinished(); +} diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java index 05c00471ee57..3c411126627f 100644 --- a/core/java/android/widget/EdgeEffect.java +++ b/core/java/android/widget/EdgeEffect.java @@ -69,7 +69,7 @@ public class EdgeEffect { * @hide */ @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S) + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BASE) public static final long USE_STRETCH_EDGE_EFFECT_BY_DEFAULT = 171228096L; /** @@ -354,13 +354,14 @@ public class EdgeEffect { mDistance = Math.max(0f, mPullDistance); mVelocity = 0; - final float absdd = Math.abs(deltaDistance); - mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA, - mGlowAlpha + (absdd * PULL_DISTANCE_ALPHA_GLOW_FACTOR)); - if (mPullDistance == 0) { mGlowScaleY = mGlowScaleYStart = 0; + mGlowAlpha = mGlowAlphaStart = 0; } else { + final float absdd = Math.abs(deltaDistance); + mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA, + mGlowAlpha + (absdd * PULL_DISTANCE_ALPHA_GLOW_FACTOR)); + final float scale = (float) (Math.max(0, 1 - 1 / Math.sqrt(Math.abs(mPullDistance) * mBounds.height()) - 0.3d) / 0.7d); @@ -698,7 +699,9 @@ public class EdgeEffect { mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp; mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp; - mDistance = calculateDistanceFromGlowValues(mGlowScaleY, mGlowAlpha); + if (mState != STATE_PULL) { + mDistance = calculateDistanceFromGlowValues(mGlowScaleY, mGlowAlpha); + } mDisplacement = (mDisplacement + mTargetDisplacement) / 2; if (t >= 1.f - EPSILON) { diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS index 718076b49f77..64570a8ad155 100644 --- a/core/java/android/widget/OWNERS +++ b/core/java/android/widget/OWNERS @@ -5,6 +5,8 @@ alanv@google.com adamp@google.com aurimas@google.com siyamed@google.com +mount@google.com +njawad@google.com per-file TextView.java, EditText.java, Editor.java = siyamed@google.com, nona@google.com, clarabayarri@google.com diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 2b73923fc5f4..0cedcea7b4d4 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -2196,7 +2196,7 @@ public class RemoteViews implements Parcelable, Filter { int recycledViewIndex = findViewIndexToRecycle(target, rvToApply); if (recycledViewIndex >= 0) { View child = target.getChildAt(recycledViewIndex); - if (getViewLayoutId(child) == rvToApply.getLayoutId()) { + if (rvToApply.canRecycleView(child)) { if (nextChild < recycledViewIndex) { target.removeViews(nextChild, recycledViewIndex - nextChild); } @@ -2254,7 +2254,7 @@ public class RemoteViews implements Parcelable, Filter { // application are placed before. ViewTree recycled = target.mChildren.get(recycledViewIndex); // We can only recycle the view if the layout id is the same. - if (getViewLayoutId(recycled.mRoot) == rvToApply.getLayoutId()) { + if (rvToApply.canRecycleView(recycled.mRoot)) { if (recycledViewIndex > nextChild) { target.removeChildren(nextChild, recycledViewIndex - nextChild); } @@ -3726,7 +3726,8 @@ public class RemoteViews implements Parcelable, Filter { * * The {@code stableId} will be used to identify a potential view to recycled when the remote * view is inflated. Views can be re-used if inserted in the same order, potentially with - * some views appearing / disappearing. + * some views appearing / disappearing. To be recycled the view must not change the layout + * used to inflate it or its view id (see {@link RemoteViews#setViewId}). * * Note: if a view is re-used, all the actions will be re-applied on it. However, its properties * are not reset, so what was applied in previous round will have an effect. As a view may be @@ -5116,6 +5117,7 @@ public class RemoteViews implements Parcelable, Filter { View v = inflater.inflate(rv.getLayoutId(), parent, false); if (mViewId != View.NO_ID) { v.setId(mViewId); + v.setTagInternal(R.id.remote_views_override_id, mViewId); } v.setTagInternal(R.id.widget_frame, rv.getLayoutId()); return v; @@ -5335,6 +5337,13 @@ public class RemoteViews implements Parcelable, Filter { reapply(context, v, handler, size, colorResources, true); } + /** @hide */ + public boolean canRecycleView(View v) { + Integer overrideIdTag = (Integer) v.getTag(R.id.remote_views_override_id); + int overrideId = overrideIdTag == null ? View.NO_ID : overrideIdTag; + return (Integer) v.getTag(R.id.widget_frame) == getLayoutId() && mViewId == overrideId; + } + // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls // should set it to false. private void reapply(Context context, View v, InteractionHandler handler, SizeF size, @@ -5347,7 +5356,7 @@ public class RemoteViews implements Parcelable, Filter { // (orientation or size), we throw an exception, since the layouts may be completely // unrelated. if (hasMultipleLayouts()) { - if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) { + if (!rvToApply.canRecycleView(v)) { throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + " that does not share the same root layout id."); } diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index da445b8b9f33..ddea64a77f4b 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -23,6 +23,7 @@ import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -46,8 +47,6 @@ import android.widget.FrameLayout; import com.android.internal.R; import com.android.internal.policy.DecorView; -import java.util.function.Consumer; - /** * <p>The view which allows an activity to customize its splash screen exit animation.</p> * @@ -79,8 +78,8 @@ public final class SplashScreenView extends FrameLayout { private Animatable mAnimatableIcon; private ValueAnimator mAnimator; - private Runnable mAnimationFinishListener; - private Consumer<Canvas> mOnDrawCallback; + // The host activity when transfer view to it. + private Activity mHostActivity; // cache original window and status private Window mWindow; private boolean mDrawBarBackground; @@ -334,6 +333,17 @@ public final class SplashScreenView extends FrameLayout { restoreSystemUIColors(); mWindow = null; } + if (mHostActivity != null) { + mHostActivity.detachSplashScreenView(); + } + } + + /** + * Called when this view is attached to an activity. + * @hide + */ + public void attachHostActivity(Activity activity) { + mHostActivity = activity; } /** diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java index 2237efc9e2b6..2f40d3b457c6 100644 --- a/core/java/com/android/internal/notification/SystemNotificationChannels.java +++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java @@ -58,6 +58,7 @@ public class SystemNotificationChannels { public static String SYSTEM_CHANGES = "SYSTEM_CHANGES"; public static String DO_NOT_DISTURB = "DO_NOT_DISTURB"; public static String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION"; + public static String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY"; public static void createAll(Context context) { final NotificationManager nm = context.getSystemService(NotificationManager.class); @@ -199,6 +200,12 @@ public class SystemNotificationChannels { newFeaturePrompt.setBlockable(true); channelsList.add(newFeaturePrompt); + final NotificationChannel accessibilitySecurityPolicyChannel = new NotificationChannel( + ACCESSIBILITY_SECURITY_POLICY, + context.getString(R.string.notification_channel_accessibility_security_policy), + NotificationManager.IMPORTANCE_LOW); + channelsList.add(accessibilitySecurityPolicyChannel); + nm.createNotificationChannels(channelsList); } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 11466f4bc042..33b55ac2f0a0 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -169,7 +169,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - static final int VERSION = 194; + static final int VERSION = 195; // The maximum number of names wakelocks we will keep track of // per uid; once the limit is reached, we batch the remaining wakelocks @@ -1009,8 +1009,12 @@ public class BatteryStatsImpl extends BatteryStats { protected @Nullable MeasuredEnergyStats mGlobalMeasuredEnergyStats; /** Last known screen state. Needed for apportioning display energy. */ int mScreenStateAtLastEnergyMeasurement = Display.STATE_UNKNOWN; + /** Bluetooth Power calculator for attributing measured bluetooth charge consumption to uids */ + @Nullable BluetoothPowerCalculator mBluetoothPowerCalculator = null; /** Cpu Power calculator for attributing measured cpu charge consumption to uids */ @Nullable CpuPowerCalculator mCpuPowerCalculator = null; + /** Wifi Power calculator for attributing measured wifi charge consumption to uids */ + @Nullable WifiPowerCalculator mWifiPowerCalculator = null; /** * These provide time bases that discount the time the device is plugged @@ -6967,6 +6971,16 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public long getBluetoothMeasuredBatteryConsumptionUC() { + return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH); + } + + @Override + public long getCpuMeasuredBatteryConsumptionUC() { + return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU); + } + + @Override public long getScreenOnMeasuredBatteryConsumptionUC() { return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON); } @@ -6977,8 +6991,8 @@ public class BatteryStatsImpl extends BatteryStats { } @Override - public long getCpuMeasuredBatteryConsumptionUC() { - return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU); + public long getWifiMeasuredBatteryConsumptionUC() { + return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_WIFI); } /** @@ -7815,6 +7829,26 @@ public class BatteryStatsImpl extends BatteryStats { return mUidMeasuredEnergyStats.getAccumulatedCustomBucketCharges(); } + @Override + public long getBluetoothMeasuredBatteryConsumptionUC() { + return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH); + } + + @Override + public long getCpuMeasuredBatteryConsumptionUC() { + return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU); + } + + @Override + public long getScreenOnMeasuredBatteryConsumptionUC() { + return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON); + } + + @Override + public long getWifiMeasuredBatteryConsumptionUC() { + return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_WIFI); + } + /** * Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time * since last marked. Also sets the mark time for both these timers. @@ -8482,16 +8516,6 @@ public class BatteryStatsImpl extends BatteryStats { } } - @Override - public long getScreenOnMeasuredBatteryConsumptionUC() { - return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON); - } - - @Override - public long getCpuMeasuredBatteryConsumptionUC() { - return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU); - } - void initNetworkActivityLocked() { detachIfNotNull(mNetworkByteActivityCounters); mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; @@ -11437,7 +11461,7 @@ public class BatteryStatsImpl extends BatteryStats { * @param info The energy information from the WiFi controller. */ public void updateWifiState(@Nullable final WifiActivityEnergyInfo info, - long elapsedRealtimeMs, long uptimeMs) { + final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs) { if (DEBUG_ENERGY) { Slog.d(TAG, "Updating wifi stats: " + Arrays.toString(mWifiIfaces)); } @@ -11459,9 +11483,21 @@ public class BatteryStatsImpl extends BatteryStats { if (delta != null) { mNetworkStatsPool.release(delta); } + if (mIgnoreNextExternalStats) { + // TODO: Strictly speaking, we should re-mark all 5 timers for each uid (and the + // global one) here like we do for display. But I'm not sure it's worth the + // complicated code for a codepath that shouldn't ever actually happen in real + // life. + } return; } + final ArrayMap<Uid, Double> uidEstimatedConsumptionMah = + (mGlobalMeasuredEnergyStats != null + && mWifiPowerCalculator != null && consumedChargeUC > 0) ? + new ArrayMap<>() : null; + double totalEstimatedConsumptionMah = 0; + SparseLongArray rxPackets = new SparseLongArray(); SparseLongArray txPackets = new SparseLongArray(); long totalTxPackets = 0; @@ -11496,6 +11532,7 @@ public class BatteryStatsImpl extends BatteryStats { mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked( entry.rxPackets); + // TODO(b/182845426): What if u was a mapped isolated uid? Shouldn't we sum? rxPackets.put(u.getUid(), entry.rxPackets); // Sum the total number of packets so that the Rx Power can @@ -11515,12 +11552,42 @@ public class BatteryStatsImpl extends BatteryStats { mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked( entry.txPackets); + // TODO(b/182845426): What if u was a mapped isolated uid? Shouldn't we sum? txPackets.put(u.getUid(), entry.txPackets); // Sum the total number of packets so that the Tx Power can // be evenly distributed amongst the apps. totalTxPackets += entry.txPackets; } + + // Calculate consumed energy for this uid. Only do so if WifiReporting isn't + // enabled (if it is, we'll do it later instead using info). + if (uidEstimatedConsumptionMah != null && info == null && !mHasWifiReporting) { + final long uidRunningMs = u.mWifiRunningTimer + .getTimeSinceMarkLocked(elapsedRealtimeMs * 1000) / 1000; + if (uidRunningMs > 0) u.mWifiRunningTimer.setMark(elapsedRealtimeMs); + + final long uidScanMs = u.mWifiScanTimer + .getTimeSinceMarkLocked(elapsedRealtimeMs * 1000) / 1000; + if (uidScanMs > 0) u.mWifiScanTimer.setMark(elapsedRealtimeMs); + + long uidBatchScanMs = 0; + for (int bn = 0; bn < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bn++) { + if (u.mWifiBatchedScanTimer[bn] != null) { + long bnMs = u.mWifiBatchedScanTimer[bn] + .getTimeSinceMarkLocked(elapsedRealtimeMs * 1000) / 1000; + if (bnMs > 0) { + u.mWifiBatchedScanTimer[bn].setMark(elapsedRealtimeMs); + } + uidBatchScanMs += bnMs; + } + } + + addDoubleToUidMap(uidEstimatedConsumptionMah, u, + mWifiPowerCalculator.calcPowerWithoutControllerDataMah( + entry.rxPackets, entry.txPackets, + uidRunningMs, uidScanMs, uidBatchScanMs)); + } } mNetworkStatsPool.release(delta); delta = null; @@ -11581,15 +11648,14 @@ public class BatteryStatsImpl extends BatteryStats { for (int i = 0; i < uidStatsSize; i++) { final Uid uid = mUidStats.valueAt(i); - long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked( + final long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked( elapsedRealtimeMs * 1000) / 1000; + long scanRxTimeSinceMarkMs = scanTimeSinceMarkMs; // not final + long scanTxTimeSinceMarkMs = scanTimeSinceMarkMs; // not final if (scanTimeSinceMarkMs > 0) { // Set the new mark so that next time we get new data since this point. uid.mWifiScanTimer.setMark(elapsedRealtimeMs); - long scanRxTimeSinceMarkMs = scanTimeSinceMarkMs; - long scanTxTimeSinceMarkMs = scanTimeSinceMarkMs; - // Our total scan time is more than the reported Tx/Rx time. // This is possible because the cost of a scan is approximate. // Let's normalize the result so that we evenly blame each app @@ -11623,6 +11689,7 @@ public class BatteryStatsImpl extends BatteryStats { // Distribute evenly the power consumed while Idle to each app holding a WiFi // lock. + long myIdleTimeMs = 0; final long wifiLockTimeSinceMarkMs = uid.mFullWifiLockTimer.getTimeSinceMarkLocked( elapsedRealtimeMs * 1000) / 1000; @@ -11630,8 +11697,7 @@ public class BatteryStatsImpl extends BatteryStats { // Set the new mark so that next time we get new data since this point. uid.mFullWifiLockTimer.setMark(elapsedRealtimeMs); - final long myIdleTimeMs = (wifiLockTimeSinceMarkMs * idleTimeMs) - / totalWifiLockTimeMs; + myIdleTimeMs = (wifiLockTimeSinceMarkMs * idleTimeMs) / totalWifiLockTimeMs; if (DEBUG_ENERGY) { Slog.d(TAG, " IdleTime for UID " + uid.getUid() + ": " + myIdleTimeMs + " ms"); @@ -11639,6 +11705,12 @@ public class BatteryStatsImpl extends BatteryStats { uid.getOrCreateWifiControllerActivityLocked().getIdleTimeCounter() .addCountLocked(myIdleTimeMs); } + + if (uidEstimatedConsumptionMah != null) { + double uidEstMah = mWifiPowerCalculator.calcPowerFromControllerDataMah( + scanRxTimeSinceMarkMs, scanTxTimeSinceMarkMs, myIdleTimeMs); + addDoubleToUidMap(uidEstimatedConsumptionMah, uid, uidEstMah); + } } if (DEBUG_ENERGY) { @@ -11658,6 +11730,11 @@ public class BatteryStatsImpl extends BatteryStats { } uid.getOrCreateWifiControllerActivityLocked().getTxTimeCounters()[0] .addCountLocked(myTxTimeMs); + if (uidEstimatedConsumptionMah != null) { + addDoubleToUidMap(uidEstimatedConsumptionMah, uid, + mWifiPowerCalculator.calcPowerFromControllerDataMah( + 0, myTxTimeMs, 0)); + } } // Distribute the remaining Rx power appropriately between all apps that received @@ -11672,6 +11749,11 @@ public class BatteryStatsImpl extends BatteryStats { } uid.getOrCreateWifiControllerActivityLocked().getRxTimeCounter() .addCountLocked(myRxTimeMs); + if (uidEstimatedConsumptionMah != null) { + addDoubleToUidMap(uidEstimatedConsumptionMah, uid, + mWifiPowerCalculator.calcPowerFromControllerDataMah( + myRxTimeMs, 0, 0)); + } } // Any left over power use will be picked up by the WiFi category in BatteryStatsHelper. @@ -11690,10 +11772,11 @@ public class BatteryStatsImpl extends BatteryStats { // POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. final double opVolt = mPowerProfile.getAveragePower( PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE) / 1000.0; + double controllerMaMs = 0; if (opVolt != 0) { // We store the power drain as mAms. - mWifiActivity.getPowerCounter().addCountLocked( - (long) (info.getControllerEnergyUsedMicroJoules() / opVolt)); + controllerMaMs = info.getControllerEnergyUsedMicroJoules() / opVolt; + mWifiActivity.getPowerCounter().addCountLocked((long) controllerMaMs); } // Converting uWs to mAms. // Conversion: (uWs * (1000ms / 1s) * (1mW / 1000uW)) / mV = mAms @@ -11705,6 +11788,29 @@ public class BatteryStatsImpl extends BatteryStats { (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR); addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mTmpRailStats.resetWifiTotalEnergyUsed(); + + if (uidEstimatedConsumptionMah != null) { + totalEstimatedConsumptionMah = Math.max(controllerMaMs / MILLISECONDS_IN_HOUR, + mWifiPowerCalculator.calcPowerFromControllerDataMah( + rxTimeMs, txTimeMs, idleTimeMs)); + } + } + + // Update the MeasuredEnergyStats information. + if (uidEstimatedConsumptionMah != null) { + mGlobalMeasuredEnergyStats.updateStandardBucket( + MeasuredEnergyStats.POWER_BUCKET_WIFI, consumedChargeUC); + + // Now calculate the consumption for each uid, according to its proportional usage. + if (!mHasWifiReporting) { + final long globalTimeMs = mGlobalWifiRunningTimer + .getTimeSinceMarkLocked(elapsedRealtimeMs * 1000) / 1000; + mGlobalWifiRunningTimer.setMark(elapsedRealtimeMs); + totalEstimatedConsumptionMah = mWifiPowerCalculator + .calcGlobalPowerWithoutControllerDataMah(globalTimeMs); + } + distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_WIFI, + consumedChargeUC, uidEstimatedConsumptionMah, totalEstimatedConsumptionMah); } } } @@ -11948,7 +12054,7 @@ public class BatteryStatsImpl extends BatteryStats { * @param info The energy information from the bluetooth controller. */ public void updateBluetoothStateLocked(@Nullable final BluetoothActivityEnergyInfo info, - long elapsedRealtimeMs, long uptimeMs) { + final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs) { if (DEBUG_ENERGY) { Slog.d(TAG, "Updating bluetooth stats: " + info); } @@ -11980,6 +12086,11 @@ public class BatteryStatsImpl extends BatteryStats { Slog.d(TAG, " Idle Time: " + idleTimeMs + " ms"); } + final ArrayMap<Uid, Double> uidEstimatedConsumptionMah = + (mGlobalMeasuredEnergyStats != null + && mBluetoothPowerCalculator != null && consumedChargeUC > 0) ? + new ArrayMap<>() : null; + long totalScanTimeMs = 0; final int uidCount = mUidStats.size(); @@ -12038,6 +12149,12 @@ public class BatteryStatsImpl extends BatteryStats { counter.getRxTimeCounter().addCountLocked(scanTimeRxSinceMarkMs); counter.getTxTimeCounters()[0].addCountLocked(scanTimeTxSinceMarkMs); + if (uidEstimatedConsumptionMah != null) { + addDoubleToUidMap(uidEstimatedConsumptionMah, u, + mBluetoothPowerCalculator.calculatePowerMah( + scanTimeRxSinceMarkMs, scanTimeTxSinceMarkMs, 0)); + } + leftOverRxTimeMs -= scanTimeRxSinceMarkMs; leftOverTxTimeMs -= scanTimeTxSinceMarkMs; } @@ -12098,6 +12215,11 @@ public class BatteryStatsImpl extends BatteryStats { Slog.d(TAG, "UID=" + uid + " rx_bytes=" + rxBytes + " rx_time=" + timeRxMs); } counter.getRxTimeCounter().addCountLocked(timeRxMs); + + if (uidEstimatedConsumptionMah != null) { + addDoubleToUidMap(uidEstimatedConsumptionMah, u, + mBluetoothPowerCalculator.calculatePowerMah(timeRxMs, 0, 0)); + } } if (totalTxBytes > 0 && txBytes > 0) { @@ -12106,6 +12228,11 @@ public class BatteryStatsImpl extends BatteryStats { Slog.d(TAG, "UID=" + uid + " tx_bytes=" + txBytes + " tx_time=" + timeTxMs); } counter.getTxTimeCounters()[0].addCountLocked(timeTxMs); + + if (uidEstimatedConsumptionMah != null) { + addDoubleToUidMap(uidEstimatedConsumptionMah, u, + mBluetoothPowerCalculator.calculatePowerMah(0, timeTxMs, 0)); + } } } } @@ -12117,12 +12244,26 @@ public class BatteryStatsImpl extends BatteryStats { // POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. final double opVolt = mPowerProfile.getAveragePower( PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE) / 1000.0; + double controllerMaMs = 0; if (opVolt != 0) { + controllerMaMs = (info.getControllerEnergyUsed() - mLastBluetoothActivityInfo.energy) + / opVolt; // We store the power drain as mAms. - mBluetoothActivity.getPowerCounter().addCountLocked( - (long) ((info.getControllerEnergyUsed() - mLastBluetoothActivityInfo.energy) - / opVolt)); + mBluetoothActivity.getPowerCounter().addCountLocked((long) controllerMaMs); + } + + // Update the MeasuredEnergyStats information. + if (uidEstimatedConsumptionMah != null) { + mGlobalMeasuredEnergyStats.updateStandardBucket( + MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH, consumedChargeUC); + + double totalEstimatedMah + = mBluetoothPowerCalculator.calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs); + totalEstimatedMah = Math.max(totalEstimatedMah, controllerMaMs / MILLISECONDS_IN_HOUR); + distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH, + consumedChargeUC, uidEstimatedConsumptionMah, totalEstimatedMah); } + mLastBluetoothActivityInfo.set(info); } @@ -12311,37 +12452,17 @@ public class BatteryStatsImpl extends BatteryStats { // If multidisplay becomes a reality, this is probably more reasonable than pooling. // On the first pass, collect total time since mark so that we can normalize power. - long totalFgTimeMs = 0L; - final ArrayMap<Uid, Long> fgTimeMsArray = new ArrayMap<>(); + final ArrayMap<Uid, Double> fgTimeUsArray = new ArrayMap<>(); final long elapsedRealtimeUs = elapsedRealtimeMs * 1000; // TODO(b/175726779): Update and optimize the algorithm (e.g. avoid iterating over ALL uids) final int uidStatsSize = mUidStats.size(); for (int i = 0; i < uidStatsSize; i++) { final Uid uid = mUidStats.valueAt(i); - final long fgTimeMs = uid.markProcessForegroundTimeUs(elapsedRealtimeMs, true) / 1000; - if (fgTimeMs == 0) continue; - fgTimeMsArray.put(uid, fgTimeMs); - totalFgTimeMs += fgTimeMs; - } - long totalDisplayChargeMC = chargeUC / 1000; // not final - - // Actually assign and distribute power usage to apps based on their fg time since mark. - // TODO(b/175726326): Decide on 'energy' units and make sure algorithm won't overflow. - final long fgTimeArraySize = fgTimeMsArray.size(); - for (int i = 0; i < fgTimeArraySize; i++) { - final Uid uid = fgTimeMsArray.keyAt(i); - final long fgTimeMs = fgTimeMsArray.valueAt(i); - - // Using long division: "appEnergy = totalEnergy * appFg/totalFg + 0.5" with rounding - final long appDisplayChargeMC = - (totalDisplayChargeMC * fgTimeMs + (totalFgTimeMs / 2)) - / totalFgTimeMs; - uid.addChargeToStandardBucketLocked(appDisplayChargeMC * 1000, powerBucket); - - // To mitigate round-off errors, remove this app from numerator & denominator totals - totalDisplayChargeMC -= appDisplayChargeMC; - totalFgTimeMs -= fgTimeMs; + final long fgTimeUs = uid.markProcessForegroundTimeUs(elapsedRealtimeMs, true); + if (fgTimeUs == 0) continue; + fgTimeUsArray.put(uid, (double) fgTimeUs); } + distributeEnergyToUidsLocked(powerBucket, chargeUC, fgTimeUsArray, 0); } /** @@ -12389,6 +12510,54 @@ public class BatteryStatsImpl extends BatteryStats { } /** + * Attributes energy (for the given bucket) to each uid according to the following formula: + * blamedEnergy[uid] = totalEnergy * ratioNumerators[uid] / ratioDenominator; + * <p>Does nothing if ratioDenominator is 0. + * + * <p>Here, ratioDenominator = max(sumOfAllRatioNumerators, minRatioDenominator), + * so if given minRatioDenominator <= 0, then sumOfAllRatioNumerators will be used implicitly. + * + * <p>Note that ratioNumerators and minRatioDenominator must use the same units, but need not + * use the same units as totalConsumedChargeUC (which must be in microcoulombs). + * + * <p>A consequence of minRatioDenominator is that the sum over all uids might be less than + * totalConsumedChargeUC. This is intentional; the remainder is purposefully unnaccounted rather + * than incorrectly blamed on uids, and implies unknown (non-uid) sources of drain. + */ + // TODO(b/182845832): Use some sort of "SparseDoubleArray" instead of ArrayMap<Uid, Double>. + private void distributeEnergyToUidsLocked(@StandardPowerBucket int bucket, + long totalConsumedChargeUC, ArrayMap<Uid, Double> ratioNumerators, + double minRatioDenominator) { + + // If the sum of all app usage was greater than the total, use that instead: + double sumRatioNumerators = 0; + for (int i = ratioNumerators.size() - 1; i >= 0; i--) { + sumRatioNumerators += ratioNumerators.valueAt(i); + } + final double ratioDenominator = Math.max(sumRatioNumerators, minRatioDenominator); + if (ratioDenominator <= 0) return; + + for (int i = ratioNumerators.size() - 1; i >= 0; i--) { + final Uid uid = ratioNumerators.keyAt(i); + final double ratioNumerator = ratioNumerators.valueAt(i); + final long uidActualUC + = (long) (totalConsumedChargeUC * ratioNumerator / ratioDenominator + 0.5); + uid.addChargeToStandardBucketLocked(uidActualUC, bucket); + } + } + + /** Adds the summand to the value stored in uidMap for the given uid. */ + // TODO(b/182845832): Use some sort of "SparseDoubleArray" instead of ArrayMap<Uid, Double>. + private static void addDoubleToUidMap(ArrayMap<Uid, Double> uidMap, Uid uid, double summand) { + if (uidMap == null) return; + final Double oldVal = uidMap.get(uid); + if (oldVal != null) { + summand += oldVal; + } + uidMap.put(uid, summand); + } + + /** * Read and record Rail Energy data. */ public void updateRailStatsLocked() { @@ -14222,15 +14391,20 @@ public class BatteryStatsImpl extends BatteryStats { if (mGlobalMeasuredEnergyStats == null) { mGlobalMeasuredEnergyStats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - return; } else { supportedBucketMismatch = !mGlobalMeasuredEnergyStats.isSupportEqualTo( supportedStandardBuckets, numCustomBuckets); } + if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH]) { + mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile); + } if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_CPU]) { mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile); } + if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_WIFI]) { + mWifiPowerCalculator = new WifiPowerCalculator(mPowerProfile); + } } if (supportedBucketMismatch) { diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java index 7d42de4486a4..db1403479f8a 100644 --- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java +++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java @@ -15,8 +15,11 @@ */ package com.android.internal.os; +import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE; + import android.os.BatteryConsumer; import android.os.BatteryStats; +import android.os.BatteryStats.ControllerActivityCounter; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Process; @@ -65,17 +68,19 @@ public class BluetoothPowerCalculator extends PowerCalculator { builder.getUidBatteryConsumerBuilders(); for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); - calculateApp(app, total); + calculateApp(app, total, query); if (app.getUid() == Process.BLUETOOTH_UID) { app.excludeFromBatteryUsageStats(); systemBatteryConsumerBuilder.addUidBatteryConsumer(app); } } - final BatteryStats.ControllerActivityCounter activityCounter = + final long measuredChargeUC = query.shouldForceUsePowerProfileModel() ? + POWER_DATA_UNAVAILABLE : batteryStats.getBluetoothMeasuredBatteryConsumptionUC(); + final ControllerActivityCounter activityCounter = batteryStats.getBluetoothControllerActivity(); final long systemDurationMs = calculateDuration(activityCounter); - final double systemPowerMah = calculatePower(activityCounter); + final double systemPowerMah = calculatePowerMah(measuredChargeUC, activityCounter); // Subtract what the apps used, but clamp to 0. final long systemComponentDurationMs = Math.max(0, systemDurationMs - total.durationMs); @@ -91,11 +96,16 @@ public class BluetoothPowerCalculator extends PowerCalculator { systemComponentPowerMah); } - private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total) { - final BatteryStats.ControllerActivityCounter activityCounter = + private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total, + BatteryUsageStatsQuery query) { + + final long measuredChargeUC = query.shouldForceUsePowerProfileModel() ? + POWER_DATA_UNAVAILABLE : + app.getBatteryStatsUid().getBluetoothMeasuredBatteryConsumptionUC(); + final ControllerActivityCounter activityCounter = app.getBatteryStatsUid().getBluetoothControllerActivity(); final long durationMs = calculateDuration(activityCounter); - final double powerMah = calculatePower(activityCounter); + final double powerMah = calculatePowerMah(measuredChargeUC, activityCounter); app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_BLUETOOTH, durationMs) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerMah); @@ -121,10 +131,11 @@ public class BluetoothPowerCalculator extends PowerCalculator { } BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0); - final BatteryStats.ControllerActivityCounter activityCounter = + final long measuredChargeUC = batteryStats.getBluetoothMeasuredBatteryConsumptionUC(); + final ControllerActivityCounter activityCounter = batteryStats.getBluetoothControllerActivity(); - final double systemPowerMah = calculatePower(activityCounter); final long systemDurationMs = calculateDuration(activityCounter); + final double systemPowerMah = calculatePowerMah(measuredChargeUC, activityCounter); // Subtract what the apps used, but clamp to 0. final double powerMah = Math.max(0, systemPowerMah - total.powerMah); @@ -152,10 +163,11 @@ public class BluetoothPowerCalculator extends PowerCalculator { private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, PowerAndDuration total) { - final BatteryStats.ControllerActivityCounter activityCounter = - u.getBluetoothControllerActivity(); + + final long measuredChargeUC = u.getBluetoothMeasuredBatteryConsumptionUC(); + final ControllerActivityCounter activityCounter = u.getBluetoothControllerActivity(); final long durationMs = calculateDuration(activityCounter); - final double powerMah = calculatePower(activityCounter); + final double powerMah = calculatePowerMah(measuredChargeUC, activityCounter); app.bluetoothRunningTimeMs = durationMs; app.bluetoothPowerMah = powerMah; @@ -166,7 +178,7 @@ public class BluetoothPowerCalculator extends PowerCalculator { total.powerMah += powerMah; } - private long calculateDuration(BatteryStats.ControllerActivityCounter counter) { + private long calculateDuration(ControllerActivityCounter counter) { if (counter == null) { return 0; } @@ -176,7 +188,11 @@ public class BluetoothPowerCalculator extends PowerCalculator { + counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED); } - private double calculatePower(BatteryStats.ControllerActivityCounter counter) { + /** Returns bluetooth power usage based on the best data available. */ + private double calculatePowerMah(long measuredChargeUC, ControllerActivityCounter counter) { + if (measuredChargeUC != POWER_DATA_UNAVAILABLE) { + return uCtoMah(measuredChargeUC); + } if (counter == null) { return 0; } @@ -195,6 +211,11 @@ public class BluetoothPowerCalculator extends PowerCalculator { counter.getRxTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED); final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED); + return calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs); + } + + /** Returns estimated bluetooth power usage based on usage times. */ + public double calculatePowerMah(long rxTimeMs, long txTimeMs, long idleTimeMs) { return ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa)) / (1000 * 60 * 60); } diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING index 791e9ad5ef9d..14cdb0890b25 100644 --- a/core/java/com/android/internal/os/TEST_MAPPING +++ b/core/java/com/android/internal/os/TEST_MAPPING @@ -1,7 +1,11 @@ { "presubmit": [ { - "file_patterns": ["Battery[^/]*\\.java"], + "file_patterns": [ + "Battery[^/]*\\.java", + "Kernel[^/]*\\.java", + "[^/]*Power[^/]*\\.java" + ], "name": "FrameworksCoreTests", "options": [ { "include-filter": "com.android.internal.os.BatteryStatsTests" }, diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java index 98f613fc1c40..b6bfde709cb8 100644 --- a/core/java/com/android/internal/os/WifiPowerCalculator.java +++ b/core/java/com/android/internal/os/WifiPowerCalculator.java @@ -15,6 +15,8 @@ */ package com.android.internal.os; +import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE; + import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStats; @@ -79,11 +81,6 @@ public class WifiPowerCalculator extends PowerCalculator { public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) { - // batteryStats.hasWifiActivityReporting can change if we get energy data at a later point, - // so always check this field. - final boolean hasWifiPowerReporting = - mHasWifiPowerController && batteryStats.hasWifiActivityReporting(); - final SystemBatteryConsumer.Builder systemBatteryConsumerBuilder = builder.getOrCreateSystemBatteryConsumerBuilder( SystemBatteryConsumer.DRAIN_TYPE_WIFI); @@ -97,7 +94,8 @@ public class WifiPowerCalculator extends PowerCalculator { final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); calculateApp(powerDurationAndTraffic, app.getBatteryStatsUid(), rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED, - hasWifiPowerReporting); + batteryStats.hasWifiActivityReporting(), + query.shouldForceUsePowerProfileModel()); totalAppDurationMs += powerDurationAndTraffic.durationMs; totalAppPowerMah += powerDurationAndTraffic.powerMah; @@ -115,7 +113,9 @@ public class WifiPowerCalculator extends PowerCalculator { calculateRemaining(powerDurationAndTraffic, batteryStats, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED, - hasWifiPowerReporting, totalAppDurationMs, totalAppPowerMah); + batteryStats.hasWifiActivityReporting(), + query.shouldForceUsePowerProfileModel(), + totalAppDurationMs, totalAppPowerMah); systemBatteryConsumerBuilder .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI, @@ -135,11 +135,6 @@ public class WifiPowerCalculator extends PowerCalculator { public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - // batteryStats.hasWifiActivityReporting can change if we get energy data at a later point, - // so always check this field. - final boolean hasWifiPowerReporting = - mHasWifiPowerController && batteryStats.hasWifiActivityReporting(); - final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.WIFI, null, 0); long totalAppDurationMs = 0; @@ -149,7 +144,7 @@ public class WifiPowerCalculator extends PowerCalculator { final BatterySipper app = sippers.get(i); if (app.drainType == BatterySipper.DrainType.APP) { calculateApp(powerDurationAndTraffic, app.uidObj, rawRealtimeUs, statsType, - hasWifiPowerReporting); + batteryStats.hasWifiActivityReporting(), /* force use power model*/ false); totalAppDurationMs += powerDurationAndTraffic.durationMs; totalAppPowerMah += powerDurationAndTraffic.powerMah; @@ -169,7 +164,8 @@ public class WifiPowerCalculator extends PowerCalculator { } calculateRemaining(powerDurationAndTraffic, batteryStats, rawRealtimeUs, statsType, - hasWifiPowerReporting, totalAppDurationMs, totalAppPowerMah); + batteryStats.hasWifiActivityReporting(), /* force use power model*/ false, + totalAppDurationMs, totalAppPowerMah); bs.wifiRunningTimeMs += powerDurationAndTraffic.durationMs; bs.wifiPowerMah += powerDurationAndTraffic.powerMah; @@ -180,8 +176,9 @@ public class WifiPowerCalculator extends PowerCalculator { } private void calculateApp(PowerDurationAndTraffic powerDurationAndTraffic, BatteryStats.Uid u, - long rawRealtimeUs, - int statsType, boolean hasWifiPowerReporting) { + long rawRealtimeUs, int statsType, + boolean hasWifiActivityReporting, boolean shouldForceUsePowerProfileModel) { + powerDurationAndTraffic.wifiRxPackets = u.getNetworkActivityPackets( BatteryStats.NETWORK_WIFI_RX_DATA, statsType); @@ -195,7 +192,14 @@ public class WifiPowerCalculator extends PowerCalculator { BatteryStats.NETWORK_WIFI_TX_DATA, statsType); - if (hasWifiPowerReporting) { + final long measuredChargeUC = u.getWifiMeasuredBatteryConsumptionUC(); + final boolean isMeasuredPowerAvailable + = !shouldForceUsePowerProfileModel && measuredChargeUC != POWER_DATA_UNAVAILABLE; + if (isMeasuredPowerAvailable) { + powerDurationAndTraffic.powerMah = uCtoMah(measuredChargeUC); + } + + if (hasWifiActivityReporting && mHasWifiPowerController) { final BatteryStats.ControllerActivityCounter counter = u.getWifiControllerActivity(); if (counter != null) { final long idleTime = counter.getIdleTimeCounter().getCountLocked(statsType); @@ -203,9 +207,10 @@ public class WifiPowerCalculator extends PowerCalculator { final long rxTime = counter.getRxTimeCounter().getCountLocked(statsType); powerDurationAndTraffic.durationMs = idleTime + rxTime + txTime; - powerDurationAndTraffic.powerMah = mIdlePowerEstimator.calculatePower(idleTime) - + mTxPowerEstimator.calculatePower(txTime) - + mRxPowerEstimator.calculatePower(rxTime); + if (!isMeasuredPowerAvailable) { + powerDurationAndTraffic.powerMah + = calcPowerFromControllerDataMah(rxTime, txTime, idleTime); + } if (DEBUG && powerDurationAndTraffic.powerMah != 0) { Log.d(TAG, "UID " + u.getUid() + ": idle=" + idleTime + "ms rx=" + rxTime @@ -214,21 +219,20 @@ public class WifiPowerCalculator extends PowerCalculator { } } } else { - final double wifiPacketPower = ( - powerDurationAndTraffic.wifiRxPackets + powerDurationAndTraffic.wifiTxPackets) - * mWifiPowerPerPacket; final long wifiRunningTime = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000; - final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType) / 1000; - long batchScanTimeMs = 0; - for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) { - batchScanTimeMs += u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000; - } - powerDurationAndTraffic.durationMs = wifiRunningTime; - powerDurationAndTraffic.powerMah = wifiPacketPower - + mPowerOnPowerEstimator.calculatePower(wifiRunningTime) - + mScanPowerEstimator.calculatePower(wifiScanTimeMs) - + mBatchScanPowerEstimator.calculatePower(batchScanTimeMs); + + if (!isMeasuredPowerAvailable) { + final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType) / 1000; + long batchTimeMs = 0; + for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) { + batchTimeMs += u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000; + } + powerDurationAndTraffic.powerMah = calcPowerWithoutControllerDataMah( + powerDurationAndTraffic.wifiRxPackets, + powerDurationAndTraffic.wifiTxPackets, + wifiRunningTime, wifiScanTimeMs, batchTimeMs); + } if (DEBUG && powerDurationAndTraffic.powerMah != 0) { Log.d(TAG, "UID " + u.getUid() + ": power=" + formatCharge( @@ -238,12 +242,20 @@ public class WifiPowerCalculator extends PowerCalculator { } private void calculateRemaining(PowerDurationAndTraffic powerDurationAndTraffic, - BatteryStats stats, long rawRealtimeUs, - int statsType, boolean hasWifiPowerReporting, long totalAppDurationMs, - double totalAppPowerMah) { + BatteryStats stats, long rawRealtimeUs, int statsType, + boolean hasWifiActivityReporting, boolean shouldForceUsePowerProfileModel, + long totalAppDurationMs, double totalAppPowerMah) { + long totalDurationMs; - double totalPowerMah; - if (hasWifiPowerReporting) { + double totalPowerMah = 0; + + final long measuredChargeUC = stats.getWifiMeasuredBatteryConsumptionUC(); + final boolean isMeasuredPowerAvailable + = !shouldForceUsePowerProfileModel && measuredChargeUC != POWER_DATA_UNAVAILABLE; + if (isMeasuredPowerAvailable) { + totalPowerMah = uCtoMah(measuredChargeUC); + } + if (hasWifiActivityReporting && mHasWifiPowerController) { final BatteryStats.ControllerActivityCounter counter = stats.getWifiControllerActivity(); @@ -253,17 +265,19 @@ public class WifiPowerCalculator extends PowerCalculator { totalDurationMs = idleTimeMs + rxTimeMs + txTimeMs; - totalPowerMah = - counter.getPowerCounter().getCountLocked(statsType) / (double) (1000 * 60 * 60); - if (totalPowerMah == 0) { - // Some controllers do not report power drain, so we can calculate it here. - totalPowerMah = mIdlePowerEstimator.calculatePower(idleTimeMs) - + mTxPowerEstimator.calculatePower(txTimeMs) - + mRxPowerEstimator.calculatePower(rxTimeMs); + if (!isMeasuredPowerAvailable) { + totalPowerMah = counter.getPowerCounter().getCountLocked(statsType) + / (double) (1000 * 60 * 60); + if (totalPowerMah == 0) { + // Some controllers do not report power drain, so we can calculate it here. + totalPowerMah = calcPowerFromControllerDataMah(rxTimeMs, txTimeMs, idleTimeMs); + } } } else { totalDurationMs = stats.getGlobalWifiRunningTime(rawRealtimeUs, statsType) / 1000; - totalPowerMah = mPowerOnPowerEstimator.calculatePower(totalDurationMs); + if (!isMeasuredPowerAvailable) { + totalPowerMah = calcGlobalPowerWithoutControllerDataMah(totalDurationMs); + } } powerDurationAndTraffic.durationMs = Math.max(0, totalDurationMs - totalAppDurationMs); @@ -274,6 +288,29 @@ public class WifiPowerCalculator extends PowerCalculator { } } + /** Returns (global or uid) estimated wifi power used using WifiControllerActivity data. */ + public double calcPowerFromControllerDataMah(long rxTimeMs, long txTimeMs, long idleTimeMs) { + return mRxPowerEstimator.calculatePower(rxTimeMs) + + mTxPowerEstimator.calculatePower(txTimeMs) + + mIdlePowerEstimator.calculatePower(idleTimeMs); + } + + /** Returns per-uid estimated wifi power used using non-WifiControllerActivity data. */ + public double calcPowerWithoutControllerDataMah(long rxPackets, long txPackets, + long wifiRunningTimeMs, long wifiScanTimeMs, long wifiBatchScanTimeMs) { + return + (rxPackets + txPackets) * mWifiPowerPerPacket + + mPowerOnPowerEstimator.calculatePower(wifiRunningTimeMs) + + mScanPowerEstimator.calculatePower(wifiScanTimeMs) + + mBatchScanPowerEstimator.calculatePower(wifiBatchScanTimeMs); + + } + + /** Returns global estimated wifi power used using non-WifiControllerActivity data. */ + public double calcGlobalPowerWithoutControllerDataMah(long globalWifiRunningTimeMs) { + return mPowerOnPowerEstimator.calculatePower(globalWifiRunningTimeMs); + } + /** * Return estimated power per Wi-Fi packet in mAh/packet where 1 packet = 2 KB. */ diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 47b0f8c2be0c..2458fe3dfb8c 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -41,7 +41,6 @@ import android.os.UserHandle; import android.os.ZygoteProcess; import android.os.storage.StorageManager; import android.provider.DeviceConfig; -import android.security.keystore.AndroidKeyStoreProvider; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; @@ -74,7 +73,6 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.security.Provider; import java.security.Security; -import java.util.Optional; /** * Startup class for the zygote process. @@ -227,17 +225,7 @@ public class ZygoteInit { // AndroidKeyStoreProvider.install() manipulates the list of JCA providers to insert // preferred providers. Note this is not done via security.properties as the JCA providers // are not on the classpath in the case of, for example, raw dalvikvm runtimes. - // TODO b/171305684 This code is used to conditionally enable the installation of the - // Keystore 2.0 provider to enable teams adjusting to Keystore 2.0 at their own - // pace. This code will be removed when all calling code was adjusted to - // Keystore 2.0. - Optional<Boolean> keystore2_enabled = - android.sysprop.Keystore2Properties.keystore2_enabled(); - if (keystore2_enabled.isPresent() && keystore2_enabled.get()) { - android.security.keystore2.AndroidKeyStoreProvider.install(); - } else { - AndroidKeyStoreProvider.install(); - } + android.security.keystore2.AndroidKeyStoreProvider.install(); Log.i(TAG, "Installed AndroidKeyStoreProvider in " + (SystemClock.uptimeMillis() - startTime) + "ms."); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java index e3d5464ca413..845b3e501c08 100644 --- a/core/java/com/android/internal/power/MeasuredEnergyStats.java +++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java @@ -52,7 +52,9 @@ public class MeasuredEnergyStats { public static final int POWER_BUCKET_SCREEN_DOZE = 1; public static final int POWER_BUCKET_SCREEN_OTHER = 2; public static final int POWER_BUCKET_CPU = 3; - public static final int NUMBER_STANDARD_POWER_BUCKETS = 4; // Buckets above this are custom. + public static final int POWER_BUCKET_WIFI = 4; + public static final int POWER_BUCKET_BLUETOOTH = 5; + public static final int NUMBER_STANDARD_POWER_BUCKETS = 6; // Buckets above this are custom. @IntDef(prefix = {"POWER_BUCKET_"}, value = { POWER_BUCKET_UNKNOWN, @@ -60,6 +62,8 @@ public class MeasuredEnergyStats { POWER_BUCKET_SCREEN_DOZE, POWER_BUCKET_SCREEN_OTHER, POWER_BUCKET_CPU, + POWER_BUCKET_WIFI, + POWER_BUCKET_BLUETOOTH, }) @Retention(RetentionPolicy.SOURCE) public @interface StandardPowerBucket { diff --git a/core/java/com/android/internal/util/LocationPermissionChecker.java b/core/java/com/android/internal/util/LocationPermissionChecker.java deleted file mode 100644 index d67bd7a853c8..000000000000 --- a/core/java/com/android/internal/util/LocationPermissionChecker.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.util; - -import android.Manifest; -import android.annotation.IntDef; -import android.annotation.Nullable; -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.content.Context; -import android.content.pm.PackageManager; -import android.location.LocationManager; -import android.net.NetworkStack; -import android.os.Binder; -import android.os.Build; -import android.os.UserHandle; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - - -/** - * The methods used for location permission and location mode checking. - * - * @hide - */ -public class LocationPermissionChecker { - - private static final String TAG = "LocationPermissionChecker"; - - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"LOCATION_PERMISSION_CHECK_STATUS_"}, value = { - SUCCEEDED, - ERROR_LOCATION_MODE_OFF, - ERROR_LOCATION_PERMISSION_MISSING, - }) - public @interface LocationPermissionCheckStatus{} - - // The location permission check succeeded. - public static final int SUCCEEDED = 0; - // The location mode turns off for the caller. - public static final int ERROR_LOCATION_MODE_OFF = 1; - // The location permission isn't granted for the caller. - public static final int ERROR_LOCATION_PERMISSION_MISSING = 2; - - private final Context mContext; - private final AppOpsManager mAppOpsManager; - - public LocationPermissionChecker(Context context) { - mContext = context; - mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); - } - - /** - * Check location permission granted by the caller. - * - * This API check if the location mode enabled for the caller and the caller has - * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION. - * - * @param pkgName package name of the application requesting access - * @param featureId The feature in the package - * @param uid The uid of the package - * @param message A message describing why the permission was checked. Only needed if this is - * not inside of a two-way binder call from the data receiver - * - * @return {@code true} returns if the caller has location permission and the location mode is - * enabled. - */ - public boolean checkLocationPermission(String pkgName, @Nullable String featureId, - int uid, @Nullable String message) { - return checkLocationPermissionInternal(pkgName, featureId, uid, message) == SUCCEEDED; - } - - /** - * Check location permission granted by the caller. - * - * This API check if the location mode enabled for the caller and the caller has - * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION. - * Compared with {@link #checkLocationPermission(String, String, int, String)}, this API returns - * the detail information about the checking result, including the reason why it's failed and - * logs the error for the caller. - * - * @param pkgName package name of the application requesting access - * @param featureId The feature in the package - * @param uid The uid of the package - * @param message A message describing why the permission was checked. Only needed if this is - * not inside of a two-way binder call from the data receiver - * - * @return {@link LocationPermissionCheckStatus} the result of the location permission check. - */ - public @LocationPermissionCheckStatus int checkLocationPermissionWithDetailInfo( - String pkgName, @Nullable String featureId, int uid, @Nullable String message) { - final int result = checkLocationPermissionInternal(pkgName, featureId, uid, message); - switch (result) { - case ERROR_LOCATION_MODE_OFF: - Log.e(TAG, "Location mode is disabled for the device"); - break; - case ERROR_LOCATION_PERMISSION_MISSING: - Log.e(TAG, "UID " + uid + " has no location permission"); - break; - } - return result; - } - - /** - * Enforce the caller has location permission. - * - * This API determines if the location mode enabled for the caller and the caller has - * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION. - * SecurityException is thrown if the caller has no permission or the location mode is disabled. - * - * @param pkgName package name of the application requesting access - * @param featureId The feature in the package - * @param uid The uid of the package - * @param message A message describing why the permission was checked. Only needed if this is - * not inside of a two-way binder call from the data receiver - */ - public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid, - @Nullable String message) throws SecurityException { - final int result = checkLocationPermissionInternal(pkgName, featureId, uid, message); - - switch (result) { - case ERROR_LOCATION_MODE_OFF: - throw new SecurityException("Location mode is disabled for the device"); - case ERROR_LOCATION_PERMISSION_MISSING: - throw new SecurityException("UID " + uid + " has no location permission"); - } - } - - private int checkLocationPermissionInternal(String pkgName, @Nullable String featureId, - int uid, @Nullable String message) { - checkPackage(uid, pkgName); - - // Apps with NETWORK_SETTINGS, NETWORK_SETUP_WIZARD, NETWORK_STACK & MAINLINE_NETWORK_STACK - // are granted a bypass. - if (checkNetworkSettingsPermission(uid) || checkNetworkSetupWizardPermission(uid) - || checkNetworkStackPermission(uid) || checkMainlineNetworkStackPermission(uid)) { - return SUCCEEDED; - } - - // Location mode must be enabled - if (!isLocationModeEnabled()) { - return ERROR_LOCATION_MODE_OFF; - } - - // LocationAccess by App: caller must have Coarse/Fine Location permission to have access to - // location information. - if (!checkCallersLocationPermission(pkgName, featureId, uid, - true /* coarseForTargetSdkLessThanQ */, message)) { - return ERROR_LOCATION_PERMISSION_MISSING; - } - return SUCCEEDED; - } - - /** - * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION or - * android.Manifest.permission.ACCESS_COARSE_LOCATION (depending on config/targetSDK level) - * and a corresponding app op is allowed for this package and uid. - * - * @param pkgName PackageName of the application requesting access - * @param featureId The feature in the package - * @param uid The uid of the package - * @param coarseForTargetSdkLessThanQ If true and the targetSDK < Q then will check for COARSE - * else (false or targetSDK >= Q) then will check for FINE - * @param message A message describing why the permission was checked. Only needed if this is - * not inside of a two-way binder call from the data receiver - */ - public boolean checkCallersLocationPermission(String pkgName, @Nullable String featureId, - int uid, boolean coarseForTargetSdkLessThanQ, @Nullable String message) { - - boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid); - - String permissionType = Manifest.permission.ACCESS_FINE_LOCATION; - if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) { - // Having FINE permission implies having COARSE permission (but not the reverse) - permissionType = Manifest.permission.ACCESS_COARSE_LOCATION; - } - if (getUidPermission(permissionType, uid) == PackageManager.PERMISSION_DENIED) { - return false; - } - - // Always checking FINE - even if will not enforce. This will record the request for FINE - // so that a location request by the app is surfaced to the user. - boolean isFineLocationAllowed = noteAppOpAllowed( - AppOpsManager.OPSTR_FINE_LOCATION, pkgName, featureId, uid, message); - if (isFineLocationAllowed) { - return true; - } - if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) { - return noteAppOpAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, pkgName, featureId, uid, - message); - } - return false; - } - - /** - * Retrieves a handle to LocationManager (if not already done) and check if location is enabled. - */ - public boolean isLocationModeEnabled() { - final LocationManager LocationManager = - (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); - try { - return LocationManager.isLocationEnabledForUser(UserHandle.of( - getCurrentUser())); - } catch (Exception e) { - Log.e(TAG, "Failure to get location mode via API, falling back to settings", e); - return false; - } - } - - private boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) { - final long ident = Binder.clearCallingIdentity(); - try { - if (mContext.getPackageManager().getApplicationInfoAsUser( - packageName, 0, - UserHandle.getUserHandleForUid(callingUid)).targetSdkVersion - < versionCode) { - return true; - } - } catch (PackageManager.NameNotFoundException e) { - // In case of exception, assume unknown app (more strict checking) - // Note: This case will never happen since checkPackage is - // called to verify validity before checking App's version. - } finally { - Binder.restoreCallingIdentity(ident); - } - return false; - } - - private boolean noteAppOpAllowed(String op, String pkgName, @Nullable String featureId, - int uid, @Nullable String message) { - return mAppOpsManager.noteOp(op, uid, pkgName, featureId, message) - == AppOpsManager.MODE_ALLOWED; - } - - private void checkPackage(int uid, String pkgName) - throws SecurityException { - if (pkgName == null) { - throw new SecurityException("Checking UID " + uid + " but Package Name is Null"); - } - mAppOpsManager.checkPackage(uid, pkgName); - } - - @VisibleForTesting - protected int getCurrentUser() { - return ActivityManager.getCurrentUser(); - } - - private int getUidPermission(String permissionType, int uid) { - // We don't care about pid, pass in -1 - return mContext.checkPermission(permissionType, -1, uid); - } - - /** - * Returns true if the |uid| holds NETWORK_SETTINGS permission. - */ - public boolean checkNetworkSettingsPermission(int uid) { - return getUidPermission(android.Manifest.permission.NETWORK_SETTINGS, uid) - == PackageManager.PERMISSION_GRANTED; - } - - /** - * Returns true if the |uid| holds NETWORK_SETUP_WIZARD permission. - */ - public boolean checkNetworkSetupWizardPermission(int uid) { - return getUidPermission(android.Manifest.permission.NETWORK_SETUP_WIZARD, uid) - == PackageManager.PERMISSION_GRANTED; - } - - /** - * Returns true if the |uid| holds NETWORK_STACK permission. - */ - public boolean checkNetworkStackPermission(int uid) { - return getUidPermission(android.Manifest.permission.NETWORK_STACK, uid) - == PackageManager.PERMISSION_GRANTED; - } - - /** - * Returns true if the |uid| holds MAINLINE_NETWORK_STACK permission. - */ - public boolean checkMainlineNetworkStackPermission(int uid) { - return getUidPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, uid) - == PackageManager.PERMISSION_GRANTED; - } - -} diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java index 276cad9f6d6d..bbd738bc9eb6 100644 --- a/core/java/com/android/internal/util/Protocol.java +++ b/core/java/com/android/internal/util/Protocol.java @@ -59,8 +59,6 @@ public class Protocol { public static final int BASE_TETHERING = 0x00050000; public static final int BASE_NSD_MANAGER = 0x00060000; public static final int BASE_NETWORK_STATE_TRACKER = 0x00070000; - public static final int BASE_CONNECTIVITY_MANAGER = 0x00080000; - public static final int BASE_NETWORK_AGENT = 0x00081000; public static final int BASE_NETWORK_FACTORY = 0x00083000; public static final int BASE_ETHERNET = 0x00084000; public static final int BASE_LOWPAN = 0x00085000; diff --git a/core/java/com/android/internal/view/inline/InlineTooltipUi.java b/core/java/com/android/internal/view/inline/InlineTooltipUi.java new file mode 100644 index 000000000000..5ec8b30d6a7b --- /dev/null +++ b/core/java/com/android/internal/view/inline/InlineTooltipUi.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.view.inline; + +import static android.view.autofill.Helper.sVerbose; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.ContextWrapper; +import android.graphics.drawable.Drawable; +import android.transition.Transition; +import android.util.Slog; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.LinearLayout; +import android.widget.PopupWindow; +import android.widget.inline.InlineContentView; + +import java.io.PrintWriter; + +/** + * UI container for the inline suggestion tooltip. + */ +public final class InlineTooltipUi extends PopupWindow implements AutoCloseable { + private static final String TAG = "InlineTooltipUi"; + + private final WindowManager mWm; + private final ViewGroup mContentContainer; + + private boolean mShowing; + + private WindowManager.LayoutParams mWindowLayoutParams; + + private final View.OnAttachStateChangeListener mAnchorOnAttachStateChangeListener = + new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + /* ignore - handled by the super class */ + } + + @Override + public void onViewDetachedFromWindow(View v) { + dismiss(); + } + }; + + private final View.OnLayoutChangeListener mAnchoredOnLayoutChangeListener = + new View.OnLayoutChangeListener() { + int mHeight; + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) { + if (mHeight != bottom - top) { + mHeight = bottom - top; + adjustPosition(); + } + } + }; + + public InlineTooltipUi(@NonNull Context context) { + mContentContainer = new LinearLayout(new ContextWrapper(context)); + mWm = context.getSystemService(WindowManager.class); + + setTouchModal(false); + setOutsideTouchable(true); + setInputMethodMode(INPUT_METHOD_NOT_NEEDED); + setFocusable(false); + } + + /** + * Sets the content view for inline suggestions tooltip + * @param v the content view of {@link android.widget.inline.InlineContentView} + */ + public void setTooltipView(@NonNull InlineContentView v) { + mContentContainer.removeAllViews(); + mContentContainer.addView(v); + mContentContainer.setVisibility(View.VISIBLE); + } + + @Override + public void close() { + hide(); + } + + @Override + protected boolean hasContentView() { + return true; + } + + @Override + protected boolean hasDecorView() { + return true; + } + + @Override + protected WindowManager.LayoutParams getDecorViewLayoutParams() { + return mWindowLayoutParams; + } + + /** + * The effective {@code update} method that should be called by its clients. + */ + public void update(View anchor) { + // set to the application type with the highest z-order + setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL); + + // The first time to show up, the height of tooltip is zero, + // so set the offset Y to 2 * anchor height. + final int achoredHeight = mContentContainer.getHeight(); + final int offsetY = (achoredHeight == 0) + ? -anchor.getHeight() << 1 : -anchor.getHeight() - achoredHeight; + if (!isShowing()) { + setWidth(WindowManager.LayoutParams.WRAP_CONTENT); + setHeight(WindowManager.LayoutParams.WRAP_CONTENT); + showAsDropDown(anchor, 0 , offsetY, Gravity.TOP | Gravity.CENTER_HORIZONTAL); + } else { + update(anchor, 0 , offsetY, WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT); + } + } + + @Override + protected void update(View anchor, WindowManager.LayoutParams params) { + // update content view for the anchor is scrolling + if (anchor.isVisibleToUser()) { + show(params); + } else { + hide(); + } + } + + @Override + public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { + if (isShowing()) { + return; + } + + setShowing(true); + setDropDown(true); + attachToAnchor(anchor, xoff, yoff, gravity); + final WindowManager.LayoutParams p = mWindowLayoutParams = createPopupLayoutParams( + anchor.getWindowToken()); + final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, + p.width, p.height, gravity, getAllowScrollingAnchorParent()); + updateAboveAnchor(aboveAnchor); + p.accessibilityIdOfAnchor = anchor.getAccessibilityViewId(); + p.packageName = anchor.getContext().getPackageName(); + show(p); + } + + @Override + protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) { + super.attachToAnchor(anchor, xoff, yoff, gravity); + anchor.addOnAttachStateChangeListener(mAnchorOnAttachStateChangeListener); + } + + @Override + protected void detachFromAnchor() { + final View anchor = getAnchor(); + if (anchor != null) { + anchor.removeOnAttachStateChangeListener(mAnchorOnAttachStateChangeListener); + } + super.detachFromAnchor(); + } + + @Override + public void dismiss() { + if (!isShowing() || isTransitioningToDismiss()) { + return; + } + + setShowing(false); + setTransitioningToDismiss(true); + + hide(); + detachFromAnchor(); + if (getOnDismissListener() != null) { + getOnDismissListener().onDismiss(); + } + } + + private void adjustPosition() { + View anchor = getAnchor(); + if (anchor == null) return; + update(anchor); + } + + private void show(WindowManager.LayoutParams params) { + if (sVerbose) { + Slog.v(TAG, "show()"); + } + mWindowLayoutParams = params; + + try { + params.packageName = "android"; + params.setTitle("Autofill Inline Tooltip"); // Title is set for debugging purposes + if (!mShowing) { + params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + mContentContainer.addOnLayoutChangeListener(mAnchoredOnLayoutChangeListener); + mWm.addView(mContentContainer, params); + mShowing = true; + } else { + mWm.updateViewLayout(mContentContainer, params); + } + } catch (WindowManager.BadTokenException e) { + Slog.d(TAG, "Failed with token " + params.token + " gone."); + } catch (IllegalStateException e) { + // WM throws an ISE if mContentView was added twice; this should never happen - + // since show() and hide() are always called in the UIThread - but when it does, + // it should not crash the system. + Slog.wtf(TAG, "Exception showing window " + params, e); + } + } + + private void hide() { + if (sVerbose) { + Slog.v(TAG, "hide()"); + } + try { + if (mShowing) { + mContentContainer.removeOnLayoutChangeListener(mAnchoredOnLayoutChangeListener); + mWm.removeView(mContentContainer); + mShowing = false; + } + } catch (IllegalStateException e) { + // WM might thrown an ISE when removing the mContentView; this should never + // happen - since show() and hide() are always called in the UIThread - but if it + // does, it should not crash the system. + Slog.e(TAG, "Exception hiding window ", e); + } + } + + @Override + public int getAnimationStyle() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public Drawable getBackground() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public View getContentView() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public float getElevation() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public Transition getEnterTransition() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public Transition getExitTransition() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setBackgroundDrawable(Drawable background) { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setContentView(View contentView) { + if (contentView != null) { + throw new IllegalStateException("You can't call this!"); + } + } + + @Override + public void setElevation(float elevation) { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setEnterTransition(Transition enterTransition) { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setExitTransition(Transition exitTransition) { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setTouchInterceptor(View.OnTouchListener l) { + throw new IllegalStateException("You can't call this!"); + } + + /** + * Dumps status + */ + public void dump(@NonNull PrintWriter pw, @Nullable String prefix) { + + pw.print(prefix); + + if (mContentContainer != null) { + pw.print(prefix); pw.print("Window: "); + final String prefix2 = prefix + " "; + pw.println(); + pw.print(prefix2); pw.print("showing: "); pw.println(mShowing); + pw.print(prefix2); pw.print("view: "); pw.println(mContentContainer); + if (mWindowLayoutParams != null) { + pw.print(prefix2); pw.print("params: "); pw.println(mWindowLayoutParams); + } + pw.print(prefix2); pw.print("screen coordinates: "); + if (mContentContainer == null) { + pw.println("N/A"); + } else { + final int[] coordinates = mContentContainer.getLocationOnScreen(); + pw.print(coordinates[0]); pw.print("x"); pw.println(coordinates[1]); + } + } + } +} diff --git a/core/java/com/android/internal/view/inline/OWNERS b/core/java/com/android/internal/view/inline/OWNERS new file mode 100644 index 000000000000..edfb2112198a --- /dev/null +++ b/core/java/com/android/internal/view/inline/OWNERS @@ -0,0 +1 @@ +include /core/java/android/view/autofill/OWNERS diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 2b665c0fe9fc..a5fbae9878ac 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -513,17 +513,17 @@ message IncidentProto { optional com.android.server.powerstats.PowerStatsServiceMeterProto powerstats_meter = 3054 [ (section).type = SECTION_DUMPSYS, - (section).args = "power_stats --proto meter" + (section).args = "powerstats --proto meter" ]; optional com.android.server.powerstats.PowerStatsServiceModelProto powerstats_model = 3055 [ (section).type = SECTION_DUMPSYS, - (section).args = "power_stats --proto model" + (section).args = "powerstats --proto model" ]; optional com.android.server.powerstats.PowerStatsServiceResidencyProto powerstats_residency = 3056 [ (section).type = SECTION_DUMPSYS, - (section).args = "power_stats --proto residency" + (section).args = "powerstats --proto residency" ]; // Dumps in text format (on userdebug and eng builds only): 4000 ~ 4999 diff --git a/core/proto/android/server/biometrics.proto b/core/proto/android/server/biometrics.proto index 900235ea65fb..bbb0edd4fd59 100644 --- a/core/proto/android/server/biometrics.proto +++ b/core/proto/android/server/biometrics.proto @@ -125,6 +125,13 @@ message SensorStateProto { // User states for this sensor. repeated UserStateProto user_states = 4; + + // True if resetLockout requires a HAT to be verified in the TEE or equivalent. + optional bool reset_lockout_requires_hardware_auth_token = 5; + + // True if a HAT is required (field above) AND a challenge needs to be generated by the + // biometric TEE (or equivalent), and wrapped within the HAT. + optional bool reset_lockout_requires_challenge = 6; } // State of a specific user for a specific sensor. diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 521d246dc0dc..57d2e658d5f1 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3707,6 +3707,13 @@ <permission android:name="android.permission.BIND_HOTWORD_DETECTION_SERVICE" android:protectionLevel="signature" /> + <!-- @SystemApi Allows an application to manage hotword detection on the device. + <p>Protection level: internal|preinstalled + @hide This is not a third-party API (intended for OEMs and system apps). + --> + <permission android:name="android.permission.MANAGE_HOTWORD_DETECTION" + android:protectionLevel="internal|preinstalled" /> + <!-- Must be required by a {@link android.service.autofill.AutofillService}, to ensure that only the system can bind to it. <p>Protection level: signature diff --git a/core/res/res/drawable/ic_accessibility_24dp.xml b/core/res/res/drawable/ic_accessibility_24dp.xml new file mode 100644 index 000000000000..51e695969c85 --- /dev/null +++ b/core/res/res/drawable/ic_accessibility_24dp.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="21dp" + android:height="21dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M20.5,6c-2.61,0.7 -5.67,1 -8.5,1S6.11,6.7 3.5,6L3,8c1.86,0.5 4,0.83 6, + 1v13h2v-6h2v6h2V9c2,-0.17 4.14,-0.5 6,-1L20.5,6zM12,6c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2s-2, + 0.9 -2,2S10.9,6 12,6z" + android:fillColor="#FF000000"/> +</vector>
\ No newline at end of file diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml index 7bc4663d1070..c3b35c81cb66 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -252,5 +252,8 @@ <item type="id" name="remote_views_next_child" /> <!-- View tag associating a view with its stable id for potential recycling. --> - <item type="id" name = "remote_views_stable_id" /> + <item type="id" name="remote_views_stable_id" /> + + <!-- View tag associating a view with its overridden id, to ensure valid recycling only. --> + <item type="id" name="remote_views_override_id" /> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 054d1080f4d4..0228dfd45972 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -743,6 +743,10 @@ magnification. [CHAR_LIMIT=NONE]--> <string name="notification_channel_accessibility_magnification">Magnification</string> + <!-- Text shown when viewing channel settings for notifications related to accessibility + security policy. [CHAR_LIMIT=NONE]--> + <string name="notification_channel_accessibility_security_policy">Accessibility security policy</string> + <!-- Label for foreground service notification when one app is running. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6826789589341671842] --> <string name="foreground_service_app_in_background"><xliff:g id="app_name">%1$s</xliff:g> is @@ -5910,4 +5914,9 @@ ul.</string> <string name="splash_screen_view_icon_description">Application icon</string> <!-- Content description for the branding image on the splash screen. [CHAR LIMIT=50] --> <string name="splash_screen_view_branding_description">Application branding image</string> + + <!-- Notification title to prompt the user that some accessibility service has view and control access. [CHAR LIMIT=50] --> + <string name="view_and_control_notification_title">Check access settings</string> + <!-- Notification content to prompt the user that some accessibility service has view and control access. [CHAR LIMIT=none] --> + <string name="view_and_control_notification_content"><xliff:g id="service_name" example="TalkBack">%s</xliff:g> can view and control your screen. Tap to review.</string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index b9357885175d..567feee31673 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3541,6 +3541,7 @@ <java-symbol type="string" name="notification_channel_system_changes" /> <java-symbol type="string" name="notification_channel_do_not_disturb" /> <java-symbol type="string" name="notification_channel_accessibility_magnification" /> + <java-symbol type="string" name="notification_channel_accessibility_security_policy" /> <java-symbol type="string" name="config_defaultAutofillService" /> <java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" /> <java-symbol type="string" name="config_defaultTextClassifierPackage" /> @@ -4322,4 +4323,10 @@ <java-symbol type="id" name="remote_views_next_child" /> <java-symbol type="id" name="remote_views_stable_id" /> + <java-symbol type="id" name="remote_views_override_id" /> + + <!-- View and control prompt --> + <java-symbol type="drawable" name="ic_accessibility_24dp" /> + <java-symbol type="string" name="view_and_control_notification_title" /> + <java-symbol type="string" name="view_and_control_notification_content" /> </resources> diff --git a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java new file mode 100644 index 000000000000..85073b0ecfdd --- /dev/null +++ b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.time; + + +import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE; +import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED; +import static android.app.time.Capabilities.CAPABILITY_POSSESSED; +import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.fail; + +import android.os.UserHandle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class TimeCapabilitiesTest { + + private static final UserHandle USER_HANDLE = UserHandle.of(332211); + + @Test + public void testBuilder() { + TimeCapabilities capabilities = new TimeCapabilities.Builder(USER_HANDLE) + .setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_NOT_APPLICABLE) + .setSuggestTimeManuallyCapability(CAPABILITY_NOT_SUPPORTED) + .build(); + + assertThat(capabilities.getConfigureAutoTimeDetectionEnabledCapability()) + .isEqualTo(CAPABILITY_NOT_APPLICABLE); + assertThat(capabilities.getSuggestTimeManuallyCapability()) + .isEqualTo(CAPABILITY_NOT_SUPPORTED); + + try { + new TimeCapabilities.Builder(USER_HANDLE) + .build(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException ignored) { + // expected + } + + try { + new TimeCapabilities.Builder(USER_HANDLE) + .setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_NOT_APPLICABLE) + .build(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException ignored) { + // expected + } + + try { + new TimeCapabilities.Builder(USER_HANDLE) + .setSuggestTimeManuallyCapability(CAPABILITY_NOT_APPLICABLE) + .build(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException ignored) { + // expected + } + } + + @Test + public void userHandle_notIgnoredInEquals() { + TimeCapabilities firstUserCapabilities = new TimeCapabilities.Builder(UserHandle.of(1)) + .setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setSuggestTimeManuallyCapability(CAPABILITY_POSSESSED) + .build(); + + TimeCapabilities secondUserCapabilities = new TimeCapabilities.Builder(UserHandle.of(2)) + .setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setSuggestTimeManuallyCapability(CAPABILITY_POSSESSED) + .build(); + + assertThat(firstUserCapabilities).isNotEqualTo(secondUserCapabilities); + } + + @Test + public void testParcelable() { + TimeCapabilities.Builder builder = new TimeCapabilities.Builder(USER_HANDLE) + .setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_NOT_SUPPORTED) + .setSuggestTimeManuallyCapability(CAPABILITY_NOT_SUPPORTED); + + assertRoundTripParcelable(builder.build()); + + builder.setSuggestTimeManuallyCapability(CAPABILITY_POSSESSED); + assertRoundTripParcelable(builder.build()); + + builder.setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_POSSESSED); + assertRoundTripParcelable(builder.build()); + } + +} diff --git a/core/tests/coretests/src/android/app/time/TimeConfigurationTest.java b/core/tests/coretests/src/android/app/time/TimeConfigurationTest.java new file mode 100644 index 000000000000..7c7cd12bcb73 --- /dev/null +++ b/core/tests/coretests/src/android/app/time/TimeConfigurationTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.time; + +import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class TimeConfigurationTest { + + @Test + public void testBuilder() { + TimeConfiguration first = new TimeConfiguration.Builder() + .setAutoDetectionEnabled(true) + .build(); + + assertThat(first.isAutoDetectionEnabled()).isTrue(); + + TimeConfiguration copyFromBuilderConfiguration = new TimeConfiguration.Builder(first) + .build(); + + assertThat(first).isEqualTo(copyFromBuilderConfiguration); + } + + @Test + public void testParcelable() { + TimeConfiguration.Builder builder = new TimeConfiguration.Builder(); + + assertRoundTripParcelable(builder.setAutoDetectionEnabled(true).build()); + + assertRoundTripParcelable(builder.setAutoDetectionEnabled(false).build()); + } + +} diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java index 01a25b27baf6..dd93997b00c5 100644 --- a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java +++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java @@ -16,8 +16,8 @@ package android.app.time; -import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED; -import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED; +import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED; +import static android.app.time.Capabilities.CAPABILITY_POSSESSED; import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable; import static org.junit.Assert.assertEquals; diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java index ee472880b79f..46e2772b30ca 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -60,6 +60,7 @@ import org.junit.runners.Suite; KernelCpuUidFreqTimeReaderTest.class, KernelCpuUidUserSysTimeReaderTest.class, KernelMemoryBandwidthStatsTest.class, + KernelSingleProcessCpuThreadReaderTest.class, KernelSingleUidTimeReaderTest.class, KernelWakelockReaderTest.class, LongSamplingCounterTest.class, @@ -69,6 +70,7 @@ import org.junit.runners.Suite; PowerProfileTest.class, ScreenPowerCalculatorTest.class, SensorPowerCalculatorTest.class, + SystemServerCpuThreadReaderTest.class, SystemServicePowerCalculatorTest.class, UserPowerCalculatorTest.class, VideoPowerCalculatorTest.class, diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java index 8aeb761ffc4d..80ab36ec84cf 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java @@ -33,11 +33,15 @@ import android.util.SparseArray; import androidx.test.InstrumentationRegistry; +import com.android.internal.power.MeasuredEnergyStats; + import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; import org.mockito.stubbing.Answer; +import java.util.Arrays; + public class BatteryUsageStatsRule implements TestRule { private final PowerProfile mPowerProfile; private final MockClocks mMockClocks = new MockClocks(); @@ -98,6 +102,16 @@ public class BatteryUsageStatsRule implements TestRule { return this; } + /** Call only after setting the power profile information. */ + public BatteryUsageStatsRule initMeasuredEnergyStatsLocked(int numCustom) { + final boolean[] supportedStandardBuckets = + new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS]; + Arrays.fill(supportedStandardBuckets, true); + mBatteryStats.initMeasuredEnergyStatsLocked(supportedStandardBuckets, numCustom); + mBatteryStats.informThatAllExternalStatsAreFlushed(); + return this; + } + public BatteryUsageStatsRule startWithScreenOn(boolean screenOn) { mScreenOn = screenOn; return this; diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java index 23ea508d19d3..33b8aedb7970 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java @@ -35,7 +35,6 @@ import org.junit.runner.RunWith; import java.util.List; @SmallTest -@SkipPresubmit("b/180015146") @RunWith(AndroidJUnit4.class) public class BatteryUsageStatsTest { @@ -102,7 +101,7 @@ public class BatteryUsageStatsTest { } public void validateBatteryUsageStats(BatteryUsageStats batteryUsageStats) { - assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(100); + assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(21500); assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(20); assertThat(batteryUsageStats.getDischargedPowerRange().getLower()).isEqualTo(1000); assertThat(batteryUsageStats.getDischargedPowerRange().getUpper()).isEqualTo(2000); @@ -128,7 +127,7 @@ public class BatteryUsageStatsTest { BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND)).isEqualTo(700); assertThat(uidBatteryConsumer.getUsageDurationForCustomComponentMillis( BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID)).isEqualTo(800); - assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(1710); + assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(1200); } else { fail("Unexpected UID " + uidBatteryConsumer.getUid()); } @@ -146,7 +145,7 @@ public class BatteryUsageStatsTest { BatteryConsumer.TIME_COMPONENT_CPU)).isEqualTo(10300); assertThat(systemBatteryConsumer.getUsageDurationForCustomComponentMillis( BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID)).isEqualTo(10400); - assertThat(systemBatteryConsumer.getConsumedPower()).isEqualTo(30510); + assertThat(systemBatteryConsumer.getConsumedPower()).isEqualTo(20300); } else { fail("Unexpected drain type " + systemBatteryConsumer.getDrainType()); } diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java index f6aa08bf0645..71d7668bbcfa 100644 --- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import android.annotation.Nullable; import android.os.BatteryConsumer; +import android.os.BatteryUsageStatsQuery; import android.os.Process; import android.os.SystemBatteryConsumer; @@ -43,7 +44,6 @@ public class BluetoothPowerCalculatorTest { .setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX, 100.0); @Test - @SkipPresubmit("b/180015146") public void testTimerBasedModel() { setDurationsAndPower(mStatsRule.getUidStats(Process.BLUETOOTH_UID) .getOrCreateBluetoothControllerActivityLocked(), @@ -60,11 +60,10 @@ public class BluetoothPowerCalculatorTest { BluetoothPowerCalculator calculator = new BluetoothPowerCalculator(mStatsRule.getPowerProfile()); - mStatsRule.apply(calculator); + mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(), + calculator); - assertBluetoothPowerAndDuration( - mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID), - 0.11388, 6000); + assertThat(mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID)).isNull(); assertBluetoothPowerAndDuration( mStatsRule.getUidBatteryConsumer(APP_UID), 0.24722, 15000); @@ -74,7 +73,6 @@ public class BluetoothPowerCalculatorTest { } @Test - @SkipPresubmit("b/180015146") public void testReportedPowerBasedModel() { setDurationsAndPower(mStatsRule.getUidStats(Process.BLUETOOTH_UID) .getOrCreateBluetoothControllerActivityLocked(), @@ -91,11 +89,10 @@ public class BluetoothPowerCalculatorTest { BluetoothPowerCalculator calculator = new BluetoothPowerCalculator(mStatsRule.getPowerProfile()); - mStatsRule.apply(calculator); + mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(), + calculator); - assertBluetoothPowerAndDuration( - mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID), - 0.1, 6000); + assertThat(mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID)).isNull(); assertBluetoothPowerAndDuration( mStatsRule.getUidBatteryConsumer(APP_UID), 0.2, 15000); diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java index 10ff3a47a7d9..496415a43a6a 100644 --- a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java @@ -101,7 +101,6 @@ public class CpuPowerCalculatorTest { } @Test - @SkipPresubmit("b/180015146") public void testTimerBasedModel() { when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true); diff --git a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java index 0c91b2959f8e..f0111171b83c 100644 --- a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java @@ -42,9 +42,12 @@ public class CustomMeasuredPowerCalculatorTest { public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(); @Test - @SkipPresubmit("b/180015146") public void testMeasuredEnergyCopiedIntoBatteryConsumers() { final BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + // For side-effect of creating a BatteryStats.Uid + batteryStats.getUidStatsLocked(APP_UID); + SparseLongArray uidEnergies = new SparseLongArray(); uidEnergies.put(APP_UID, 30_000_000); batteryStats.updateCustomMeasuredEnergyStatsLocked(0, 100_000_000, uidEnergies); @@ -60,18 +63,18 @@ public class CustomMeasuredPowerCalculatorTest { UidBatteryConsumer uid = mStatsRule.getUidBatteryConsumer(APP_UID); assertThat(uid.getConsumedPowerForCustomComponent( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)) - .isWithin(PRECISION).of(2.252252); + .isWithin(PRECISION).of(8.333333); assertThat(uid.getConsumedPowerForCustomComponent( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1)) - .isWithin(PRECISION).of(9.009009); + .isWithin(PRECISION).of(33.33333); SystemBatteryConsumer systemConsumer = mStatsRule.getSystemBatteryConsumer( SystemBatteryConsumer.DRAIN_TYPE_CUSTOM); assertThat(systemConsumer.getConsumedPowerForCustomComponent( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)) - .isWithin(PRECISION).of(7.5075075); + .isWithin(PRECISION).of(27.77777); assertThat(systemConsumer.getConsumedPowerForCustomComponent( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1)) - .isWithin(PRECISION).of(15.015015); + .isWithin(PRECISION).of(55.55555); } } diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java index a47c4d832083..26adbe9e7c59 100644 --- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java +++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java @@ -46,6 +46,7 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { initTimersAndCounters(); setExternalStatsSyncLocked(new DummyExternalStatsSync()); + informThatAllExternalStatsAreFlushed(); final boolean[] supportedStandardBuckets = new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS]; diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java index 6edbbb0ad789..58e2513897ac 100644 --- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java @@ -78,7 +78,6 @@ public class SystemServicePowerCalculatorTest { } @Test - @SkipPresubmit("b/180015146") public void testPowerProfileBasedModel() { when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true); @@ -154,7 +153,7 @@ public class SystemServicePowerCalculatorTest { } @Override - public void readDelta(@Nullable Callback<long[]> cb) { + public void readDelta(boolean forcedRead, @Nullable Callback<long[]> cb) { if (cb != null) { cb.onUidCpuTime(Process.SYSTEM_UID, mSystemServerCpuTimes); } diff --git a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java index e1005457c289..2e23dc8dbba8 100644 --- a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java @@ -17,11 +17,14 @@ package com.android.internal.os; +import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE; + import static com.google.common.truth.Truth.assertThat; import android.net.NetworkCapabilities; import android.net.NetworkStats; import android.os.BatteryConsumer; +import android.os.BatteryUsageStatsQuery; import android.os.Process; import android.os.SystemBatteryConsumer; import android.os.UidBatteryConsumer; @@ -50,10 +53,11 @@ public class WifiPowerCalculatorTest { .setAveragePower(PowerProfile.POWER_WIFI_ON, 360.0) .setAveragePower(PowerProfile.POWER_WIFI_SCAN, 480.0) .setAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, 720.0) - .setAveragePower(PowerProfile.POWER_WIFI_ACTIVE, 1080.0); + .setAveragePower(PowerProfile.POWER_WIFI_ACTIVE, 1080.0) + .initMeasuredEnergyStatsLocked(0); - @Test - public void testPowerControllerBasedModel() { + /** Sets up a batterystats object with pre-populated network values. */ + private BatteryStatsImpl setupTestNetworkNumbers() { BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); batteryStats.noteNetworkInterfaceForTransports("wifi", @@ -64,13 +68,25 @@ public class WifiPowerCalculatorTest { .insertEntry("wifi", Process.WIFI_UID, 0, 0, 1111, 111, 2222, 22, 111); mStatsRule.setNetworkStats(networkStats); - WifiActivityEnergyInfo energyInfo = new WifiActivityEnergyInfo(10000, + return batteryStats; + } + + /** Sets up an WifiActivityEnergyInfo for ActivityController-model-based tests. */ + private WifiActivityEnergyInfo setupPowerControllerBasedModelEnergyNumbersInfo() { + return new WifiActivityEnergyInfo(10000, WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000); + } + + @Test + public void testPowerControllerBasedModel_nonMeasured() { + final BatteryStatsImpl batteryStats = setupTestNetworkNumbers(); + final WifiActivityEnergyInfo energyInfo = setupPowerControllerBasedModelEnergyNumbersInfo(); - batteryStats.updateWifiState(energyInfo, 1000, 1000); + batteryStats.updateWifiState(energyInfo, POWER_DATA_UNAVAILABLE, 1000, 1000); WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile()); - mStatsRule.apply(calculator); + mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(), + calculator); UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) @@ -87,30 +103,54 @@ public class WifiPowerCalculatorTest { } @Test - public void testTimerBasedModel() { - BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + public void testPowerControllerBasedModel_measured() { + final BatteryStatsImpl batteryStats = setupTestNetworkNumbers(); + final WifiActivityEnergyInfo energyInfo = setupPowerControllerBasedModelEnergyNumbersInfo(); - batteryStats.noteNetworkInterfaceForTransports("wifi", - new int[]{NetworkCapabilities.TRANSPORT_WIFI}); + batteryStats.updateWifiState(energyInfo, 1_000_000, 1000, 1000); - NetworkStats networkStats = new NetworkStats(10000, 1) - .insertEntry("wifi", APP_UID, 0, 0, 1000, 100, 2000, 20, 100) - .insertEntry("wifi", Process.WIFI_UID, 0, 0, 1111, 111, 2222, 22, 111); - mStatsRule.setNetworkStats(networkStats); + WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile()); + mStatsRule.apply(calculator); + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(1423); + /* Same ratio as in testPowerControllerBasedModel_nonMeasured but scaled by 1_000_000uC. */ + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.2214666 / (0.2214666 + 0.645200) * 1_000_000 / 3600000); + + SystemBatteryConsumer systemConsumer = + mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI); + assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(5577); + /* Same ratio as in testPowerControllerBasedModel_nonMeasured but scaled by 1_000_000uC. */ + assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.645200 / (0.2214666 + 0.645200) * 1_000_000 / 3600000); + } + + /** Sets up batterystats object with prepopulated network & timer data for Timer-model tests. */ + private BatteryStatsImpl setupTimerBasedModelTestNumbers() { + final BatteryStatsImpl batteryStats = setupTestNetworkNumbers(); batteryStats.noteWifiScanStartedLocked(APP_UID, 1000, 1000); batteryStats.noteWifiScanStoppedLocked(APP_UID, 2000, 2000); batteryStats.noteWifiRunningLocked(new WorkSource(APP_UID), 3000, 3000); batteryStats.noteWifiStoppedLocked(new WorkSource(APP_UID), 4000, 4000); batteryStats.noteWifiRunningLocked(new WorkSource(Process.WIFI_UID), 1111, 2222); batteryStats.noteWifiStoppedLocked(new WorkSource(Process.WIFI_UID), 3333, 4444); + return batteryStats; + } + + @Test + public void testTimerBasedModel_nonMeasured() { + final BatteryStatsImpl batteryStats = setupTimerBasedModelTestNumbers(); // Don't pass WifiActivityEnergyInfo, making WifiPowerCalculator rely exclusively // on the packet counts. - batteryStats.updateWifiState(/* energyInfo */ null, 1000, 1000); + batteryStats.updateWifiState(/* energyInfo */ null, POWER_DATA_UNAVAILABLE, 1000, 1000); WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile()); - mStatsRule.apply(calculator); + mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(), + calculator); UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) @@ -125,4 +165,31 @@ public class WifiPowerCalculatorTest { assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) .isWithin(PRECISION).of(0.8759216); } + + @Test + public void testTimerBasedModel_measured() { + final BatteryStatsImpl batteryStats = setupTimerBasedModelTestNumbers(); + + // Don't pass WifiActivityEnergyInfo, making WifiPowerCalculator rely exclusively + // on the packet counts. + batteryStats.updateWifiState(/* energyInfo */ null, 1_000_000, 1000, 1000); + + WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile()); + mStatsRule.apply(calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(1000); + /* Same ratio as in testTimerBasedModel_nonMeasured but scaled by 1_000_000uC. */ + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.8231573 / (0.8231573 + 0.8759216) * 1_000_000 / 3600000); + + SystemBatteryConsumer systemConsumer = + mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI); + assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(2222); + /* Same ratio as in testTimerBasedModel_nonMeasured but scaled by 1_000_000uC. */ + assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.8759216 / (0.8231573 + 0.8759216) * 1_000_000 / 3600000); + } } diff --git a/core/tests/utiltests/src/com/android/internal/util/LocationPermissionCheckerTest.java b/core/tests/utiltests/src/com/android/internal/util/LocationPermissionCheckerTest.java deleted file mode 100644 index 7175f562d7ef..000000000000 --- a/core/tests/utiltests/src/com/android/internal/util/LocationPermissionCheckerTest.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.util; - -import static android.Manifest.permission.NETWORK_SETTINGS; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.Manifest; -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.location.LocationManager; -import android.os.Binder; -import android.os.Build; -import android.os.UserHandle; -import android.os.UserManager; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.util.HashMap; - -/** Unit tests for {@link LocationPermissionChecker}. */ -public class LocationPermissionCheckerTest { - - public static final String TAG = "ConnectivityUtilTest"; - - // Mock objects for testing - @Mock private Context mMockContext; - @Mock private PackageManager mMockPkgMgr; - @Mock private ApplicationInfo mMockApplInfo; - @Mock private AppOpsManager mMockAppOps; - @Mock private UserManager mMockUserManager; - @Mock private LocationManager mLocationManager; - - private static final String TEST_PKG_NAME = "com.google.somePackage"; - private static final String TEST_FEATURE_ID = "com.google.someFeature"; - private static final int MANAGED_PROFILE_UID = 1100000; - private static final int OTHER_USER_UID = 1200000; - - private final String mInteractAcrossUsersFullPermission = - "android.permission.INTERACT_ACROSS_USERS_FULL"; - private final String mManifestStringCoarse = - Manifest.permission.ACCESS_COARSE_LOCATION; - private final String mManifestStringFine = - Manifest.permission.ACCESS_FINE_LOCATION; - - // Test variables - private int mWifiScanAllowApps; - private int mUid; - private int mCoarseLocationPermission; - private int mAllowCoarseLocationApps; - private int mFineLocationPermission; - private int mAllowFineLocationApps; - private int mNetworkSettingsPermission; - private int mCurrentUser; - private boolean mIsLocationEnabled; - private boolean mThrowSecurityException; - private Answer<Integer> mReturnPermission; - private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>(); - private LocationPermissionChecker mChecker; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - initTestVars(); - } - - private void setupMocks() throws Exception { - when(mMockPkgMgr.getApplicationInfoAsUser(eq(TEST_PKG_NAME), eq(0), any())) - .thenReturn(mMockApplInfo); - when(mMockContext.getPackageManager()).thenReturn(mMockPkgMgr); - when(mMockAppOps.noteOp(AppOpsManager.OPSTR_WIFI_SCAN, mUid, TEST_PKG_NAME, - TEST_FEATURE_ID, null)).thenReturn(mWifiScanAllowApps); - when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_COARSE_LOCATION), eq(mUid), - eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class))) - .thenReturn(mAllowCoarseLocationApps); - when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_FINE_LOCATION), eq(mUid), - eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class))) - .thenReturn(mAllowFineLocationApps); - if (mThrowSecurityException) { - doThrow(new SecurityException("Package " + TEST_PKG_NAME + " doesn't belong" - + " to application bound to user " + mUid)) - .when(mMockAppOps).checkPackage(mUid, TEST_PKG_NAME); - } - when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)) - .thenReturn(mMockAppOps); - when(mMockContext.getSystemService(Context.USER_SERVICE)) - .thenReturn(mMockUserManager); - when(mMockContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager); - } - - private void setupTestCase() throws Exception { - setupMocks(); - setupMockInterface(); - mChecker = new LocationPermissionChecker(mMockContext); - } - - private void initTestVars() { - mPermissionsList.clear(); - mReturnPermission = createPermissionAnswer(); - mWifiScanAllowApps = AppOpsManager.MODE_ERRORED; - mUid = OTHER_USER_UID; - mThrowSecurityException = true; - mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.M; - mIsLocationEnabled = false; - mCurrentUser = ActivityManager.getCurrentUser(); - mCoarseLocationPermission = PackageManager.PERMISSION_DENIED; - mFineLocationPermission = PackageManager.PERMISSION_DENIED; - mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED; - mAllowFineLocationApps = AppOpsManager.MODE_ERRORED; - mNetworkSettingsPermission = PackageManager.PERMISSION_DENIED; - } - - private void setupMockInterface() { - Binder.restoreCallingIdentity((((long) mUid) << 32) | Binder.getCallingPid()); - doAnswer(mReturnPermission).when(mMockContext).checkPermission( - anyString(), anyInt(), anyInt()); - when(mMockUserManager.isSameProfileGroup(UserHandle.SYSTEM, - UserHandle.getUserHandleForUid(MANAGED_PROFILE_UID))) - .thenReturn(true); - when(mMockContext.checkPermission(mManifestStringCoarse, -1, mUid)) - .thenReturn(mCoarseLocationPermission); - when(mMockContext.checkPermission(mManifestStringFine, -1, mUid)) - .thenReturn(mFineLocationPermission); - when(mMockContext.checkPermission(NETWORK_SETTINGS, -1, mUid)) - .thenReturn(mNetworkSettingsPermission); - when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(mIsLocationEnabled); - } - - private Answer<Integer> createPermissionAnswer() { - return new Answer<Integer>() { - @Override - public Integer answer(InvocationOnMock invocation) { - int myUid = (int) invocation.getArguments()[1]; - String myPermission = (String) invocation.getArguments()[0]; - mPermissionsList.get(myPermission); - if (mPermissionsList.containsKey(myPermission)) { - int uid = mPermissionsList.get(myPermission); - if (myUid == uid) { - return PackageManager.PERMISSION_GRANTED; - } - } - return PackageManager.PERMISSION_DENIED; - } - }; - } - - @Test - public void testEnforceLocationPermission_HasAllPermissions_BeforeQ() throws Exception { - mIsLocationEnabled = true; - mThrowSecurityException = false; - mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED; - mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED; - mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; - mUid = mCurrentUser; - setupTestCase(); - - final int result = - mChecker.checkLocationPermissionWithDetailInfo( - TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); - assertEquals(LocationPermissionChecker.SUCCEEDED, result); - } - - @Test - public void testEnforceLocationPermission_HasAllPermissions_AfterQ() throws Exception { - mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q; - mIsLocationEnabled = true; - mThrowSecurityException = false; - mUid = mCurrentUser; - mFineLocationPermission = PackageManager.PERMISSION_GRANTED; - mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED; - mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; - setupTestCase(); - - final int result = - mChecker.checkLocationPermissionWithDetailInfo( - TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); - assertEquals(LocationPermissionChecker.SUCCEEDED, result); - } - - @Test - public void testEnforceLocationPermission_PkgNameAndUidMismatch() throws Exception { - mThrowSecurityException = true; - mIsLocationEnabled = true; - mFineLocationPermission = PackageManager.PERMISSION_GRANTED; - mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED; - mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; - setupTestCase(); - - assertThrows(SecurityException.class, - () -> mChecker.checkLocationPermissionWithDetailInfo( - TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null)); - } - - @Test - public void testenforceCanAccessScanResults_NoCoarseLocationPermission() throws Exception { - mThrowSecurityException = false; - mIsLocationEnabled = true; - setupTestCase(); - - final int result = - mChecker.checkLocationPermissionWithDetailInfo( - TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); - assertEquals(LocationPermissionChecker.ERROR_LOCATION_PERMISSION_MISSING, result); - } - - @Test - public void testenforceCanAccessScanResults_NoFineLocationPermission() throws Exception { - mThrowSecurityException = false; - mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q; - mIsLocationEnabled = true; - mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED; - mAllowFineLocationApps = AppOpsManager.MODE_ERRORED; - mUid = MANAGED_PROFILE_UID; - setupTestCase(); - - final int result = - mChecker.checkLocationPermissionWithDetailInfo( - TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); - assertEquals(LocationPermissionChecker.ERROR_LOCATION_PERMISSION_MISSING, result); - verify(mMockAppOps, never()).noteOp(anyInt(), anyInt(), anyString()); - } - - @Test - public void testenforceCanAccessScanResults_LocationModeDisabled() throws Exception { - mThrowSecurityException = false; - mUid = MANAGED_PROFILE_UID; - mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; - mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid); - mIsLocationEnabled = false; - - setupTestCase(); - - final int result = - mChecker.checkLocationPermissionWithDetailInfo( - TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); - assertEquals(LocationPermissionChecker.ERROR_LOCATION_MODE_OFF, result); - } - - @Test - public void testenforceCanAccessScanResults_LocationModeDisabledHasNetworkSettings() - throws Exception { - mThrowSecurityException = false; - mIsLocationEnabled = false; - mNetworkSettingsPermission = PackageManager.PERMISSION_GRANTED; - setupTestCase(); - - final int result = - mChecker.checkLocationPermissionWithDetailInfo( - TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); - assertEquals(LocationPermissionChecker.SUCCEEDED, result); - } - - - private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) { - try { - r.run(); - Assert.fail("Expected " + exceptionClass + " to be thrown."); - } catch (Exception exception) { - assertTrue(exceptionClass.isInstance(exception)); - } - } -} diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp index 0268953eab42..8991a6117306 100644 --- a/data/etc/car/Android.bp +++ b/data/etc/car/Android.bp @@ -24,6 +24,14 @@ package { } prebuilt_etc { + name: "allowed_privapp_com.android.carsystemui", + system_ext_specific: true, + sub_dir: "permissions", + src: "com.android.carsystemui.xml", + filename_from_src: true, +} + +prebuilt_etc { name: "allowed_privapp_android.car.cluster.loggingrenderer", sub_dir: "permissions", src: "android.car.cluster.loggingrenderer.xml", diff --git a/data/etc/car/com.android.carsystemui.xml b/data/etc/car/com.android.carsystemui.xml new file mode 100644 index 000000000000..4e2f29438531 --- /dev/null +++ b/data/etc/car/com.android.carsystemui.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 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 + --> +<permissions> + <privapp-permissions package="com.android.systemui"> + <permission name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"/> + <permission name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"/> + <permission name="android.car.permission.CAR_ENROLL_TRUST"/> + <permission name="android.car.permission.CAR_POWER"/> + <permission name="android.car.permission.CONTROL_CAR_CLIMATE"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index a7b6636a15de..31cdaeb84077 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -490,6 +490,8 @@ applications that come with the platform <permission name="android.permission.SET_CLIP_SOURCE" /> <!-- Permission required for CTS test - FontManagerTest --> <permission name="android.permission.UPDATE_FONTS" /> + <!-- Permission required for hotword detection service CTS tests --> + <permission name="android.permission.MANAGE_HOTWORD_DETECTION" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 115bd9b08a53..6076c2ad1a1d 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -3307,6 +3307,12 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/TransitionController.java" }, + "1804245629": { + "message": "Attempted to add starting window to token but already cleaned", + "level": "WARN", + "group": "WM_ERROR", + "at": "com\/android\/server\/wm\/WindowManagerService.java" + }, "1810019902": { "message": "TRANSIT_FLAG_OPEN_BEHIND, adding %s to mOpeningApps", "level": "DEBUG", diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 937f01ce3767..a08f390c9fd3 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -207,7 +207,7 @@ public class KeyStore { case UserState.LSKF_LOCKED: return KeyStore.State.LOCKED; default: - throw new AssertionError(KeyStore.VALUE_CORRUPTED); + throw new AssertionError(userState); } } ret = mBinder.getState(userId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 44dad57ea3b8..c1a93ce753a6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -73,6 +73,7 @@ public class OneHandedController { private final Context mContext; private final DisplayController mDisplayController; private final OneHandedGestureHandler mGestureHandler; + private final OneHandedSettingsUtil mOneHandedSettingsUtil; private final OneHandedTimeoutHandler mTimeoutHandler; private final OneHandedTouchHandler mTouchHandler; private final OneHandedTutorialHandler mTutorialHandler; @@ -108,8 +109,12 @@ public class OneHandedController { new AccessibilityManager.AccessibilityStateChangeListener() { @Override public void onAccessibilityStateChanged(boolean enabled) { + if (mOneHandedSettingsUtil == null) { + Slog.w(TAG, "mOneHandedSettingsUtil may not instantiate yet"); + return; + } if (enabled) { - final int mOneHandedTimeout = OneHandedSettingsUtil + final int mOneHandedTimeout = mOneHandedSettingsUtil .getSettingsOneHandedModeTimeout(mContext.getContentResolver()); final int timeout = mAccessibilityManager .getRecommendedTimeoutMillis(mOneHandedTimeout * 1000 @@ -117,7 +122,7 @@ public class OneHandedController { AccessibilityManager.FLAG_CONTENT_CONTROLS); mTimeoutHandler.setTimeout(timeout / 1000); } else { - mTimeoutHandler.setTimeout(OneHandedSettingsUtil + mTimeoutHandler.setTimeout(mOneHandedSettingsUtil .getSettingsOneHandedModeTimeout(mContext.getContentResolver())); } } @@ -166,13 +171,14 @@ public class OneHandedController { OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer( context, windowManager, animationController, tutorialHandler, oneHandedBackgroundPanelOrganizer, mainExecutor); + OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil(); OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger); IOverlayManager overlayManager = IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE)); return new OneHandedController(context, windowManager, displayController, oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler, - gestureHandler, timeoutHandler, oneHandedUiEventsLogger, overlayManager, - taskStackListener, mainExecutor, mainHandler); + gestureHandler, settingsUtil, timeoutHandler, oneHandedUiEventsLogger, + overlayManager, taskStackListener, mainExecutor, mainHandler); } @VisibleForTesting @@ -184,6 +190,7 @@ public class OneHandedController { OneHandedTouchHandler touchHandler, OneHandedTutorialHandler tutorialHandler, OneHandedGestureHandler gestureHandler, + OneHandedSettingsUtil settingsUtil, OneHandedTimeoutHandler timeoutHandler, OneHandedUiEventLogger uiEventsLogger, IOverlayManager overlayManager, @@ -191,6 +198,7 @@ public class OneHandedController { ShellExecutor mainExecutor, Handler mainHandler) { mContext = context; + mOneHandedSettingsUtil = settingsUtil; mWindowManager = windowManager; mBackgroundPanelOrganizer = backgroundPanelOrganizer; mDisplayAreaOrganizer = displayAreaOrganizer; @@ -209,10 +217,10 @@ public class OneHandedController { final int sysPropPercentageConfig = SystemProperties.getInt( ONE_HANDED_MODE_OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f)); mOffSetFraction = sysPropPercentageConfig / 100.0f; - mIsOneHandedEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled( + mIsOneHandedEnabled = mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled( context.getContentResolver()); mIsSwipeToNotificationEnabled = - OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( + mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( context.getContentResolver()); mTimeoutHandler = timeoutHandler; @@ -325,25 +333,25 @@ public class OneHandedController { } private void setupSettingObservers() { - OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED, + mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED, mContext.getContentResolver(), mEnabledObserver); - OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_TIMEOUT, + mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_TIMEOUT, mContext.getContentResolver(), mTimeoutObserver); - OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.TAPS_APP_TO_EXIT, + mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.TAPS_APP_TO_EXIT, mContext.getContentResolver(), mTaskChangeExitObserver); - OneHandedSettingsUtil.registerSettingsKeyObserver( + mOneHandedSettingsUtil.registerSettingsKeyObserver( Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, mContext.getContentResolver(), mSwipeToNotificationEnabledObserver); } private void updateSettings() { - setOneHandedEnabled(OneHandedSettingsUtil + setOneHandedEnabled(mOneHandedSettingsUtil .getSettingsOneHandedModeEnabled(mContext.getContentResolver())); - mTimeoutHandler.setTimeout(OneHandedSettingsUtil + mTimeoutHandler.setTimeout(mOneHandedSettingsUtil .getSettingsOneHandedModeTimeout(mContext.getContentResolver())); - setTaskChangeToExit(OneHandedSettingsUtil + setTaskChangeToExit(mOneHandedSettingsUtil .getSettingsTapsAppToExit(mContext.getContentResolver())); - setSwipeToNotificationEnabled(OneHandedSettingsUtil + setSwipeToNotificationEnabled(mOneHandedSettingsUtil .getSettingsSwipeToNotificationEnabled(mContext.getContentResolver())); } @@ -358,7 +366,7 @@ public class OneHandedController { @VisibleForTesting void onEnabledSettingChanged() { - final boolean enabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled( + final boolean enabled = mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled( mContext.getContentResolver()); mOneHandedUiEventLogger.writeEvent(enabled ? OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_ENABLED_ON @@ -368,13 +376,13 @@ public class OneHandedController { // Also checks swipe to notification settings since they all need gesture overlay. setEnabledGesturalOverlay( - enabled || OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( + enabled || mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( mContext.getContentResolver())); } @VisibleForTesting void onTimeoutSettingChanged() { - final int newTimeout = OneHandedSettingsUtil.getSettingsOneHandedModeTimeout( + final int newTimeout = mOneHandedSettingsUtil.getSettingsOneHandedModeTimeout( mContext.getContentResolver()); int metricsId = OneHandedUiEventLogger.OneHandedSettingsTogglesEvent.INVALID.getId(); switch (newTimeout) { @@ -403,7 +411,7 @@ public class OneHandedController { @VisibleForTesting void onTaskChangeExitSettingChanged() { - final boolean enabled = OneHandedSettingsUtil.getSettingsTapsAppToExit( + final boolean enabled = mOneHandedSettingsUtil.getSettingsTapsAppToExit( mContext.getContentResolver()); mOneHandedUiEventLogger.writeEvent(enabled ? OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_APP_TAPS_EXIT_ON @@ -415,13 +423,13 @@ public class OneHandedController { @VisibleForTesting void onSwipeToNotificationEnabledSettingChanged() { final boolean enabled = - OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( + mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( mContext.getContentResolver()); setSwipeToNotificationEnabled(enabled); // Also checks one handed mode settings since they all need gesture overlay. setEnabledGesturalOverlay( - enabled || OneHandedSettingsUtil.getSettingsOneHandedModeEnabled( + enabled || mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled( mContext.getContentResolver())); } @@ -480,7 +488,8 @@ public class OneHandedController { } private void setupGesturalOverlay() { - if (!OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(mContext.getContentResolver())) { + if (!mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled( + mContext.getContentResolver())) { return; } @@ -551,7 +560,7 @@ public class OneHandedController { mTutorialHandler.dump(pw); } - OneHandedSettingsUtil.dump(pw, innerPrefix, mContext.getContentResolver()); + mOneHandedSettingsUtil.dump(pw, innerPrefix, mContext.getContentResolver()); if (mOverlayManager != null) { OverlayInfo info = null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java index f8217c64e53d..4768cf58152b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java @@ -67,7 +67,7 @@ public final class OneHandedSettingsUtil { * @param observer Observer from caller * @return uri key for observing */ - public static Uri registerSettingsKeyObserver(String key, ContentResolver resolver, + public Uri registerSettingsKeyObserver(String key, ContentResolver resolver, ContentObserver observer) { Uri uriKey = null; uriKey = Settings.Secure.getUriFor(key); @@ -83,7 +83,7 @@ public final class OneHandedSettingsUtil { * @param resolver ContentResolver of context * @param observer preference key change observer */ - public static void unregisterSettingsKeyObserver(ContentResolver resolver, + public void unregisterSettingsKeyObserver(ContentResolver resolver, ContentObserver observer) { if (resolver != null) { resolver.unregisterContentObserver(observer); @@ -95,7 +95,7 @@ public final class OneHandedSettingsUtil { * * @return enable or disable one handed mode flag. */ - public static boolean getSettingsOneHandedModeEnabled(ContentResolver resolver) { + public boolean getSettingsOneHandedModeEnabled(ContentResolver resolver) { return Settings.Secure.getInt(resolver, Settings.Secure.ONE_HANDED_MODE_ENABLED, 0 /* Disabled */) == 1; } @@ -105,7 +105,7 @@ public final class OneHandedSettingsUtil { * * @return enable or disable taps app exit. */ - public static boolean getSettingsTapsAppToExit(ContentResolver resolver) { + public boolean getSettingsTapsAppToExit(ContentResolver resolver) { return Settings.Secure.getInt(resolver, Settings.Secure.TAPS_APP_TO_EXIT, 0) == 1; } @@ -116,7 +116,7 @@ public final class OneHandedSettingsUtil { * * @return timeout value in seconds. */ - public static @OneHandedTimeout int getSettingsOneHandedModeTimeout(ContentResolver resolver) { + public @OneHandedTimeout int getSettingsOneHandedModeTimeout(ContentResolver resolver) { return Settings.Secure.getInt(resolver, Settings.Secure.ONE_HANDED_MODE_TIMEOUT, ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS); } @@ -124,12 +124,12 @@ public final class OneHandedSettingsUtil { /** * Returns whether swipe bottom to notification gesture enabled or not. */ - public static boolean getSettingsSwipeToNotificationEnabled(ContentResolver resolver) { + public boolean getSettingsSwipeToNotificationEnabled(ContentResolver resolver) { return Settings.Secure.getInt(resolver, Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1) == 1; } - protected static void dump(PrintWriter pw, String prefix, ContentResolver resolver) { + void dump(PrintWriter pw, String prefix, ContentResolver resolver) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); pw.print(innerPrefix + "isOneHandedModeEnable="); @@ -139,6 +139,6 @@ public final class OneHandedSettingsUtil { pw.print(innerPrefix + "tapsAppToExit="); pw.println(getSettingsTapsAppToExit(resolver)); } - - private OneHandedSettingsUtil() {} + public OneHandedSettingsUtil() { + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 50d8098e1d34..9212c4b86105 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -69,9 +69,6 @@ public class StartingSurfaceDrawer { private final ShellExecutor mSplashScreenExecutor; private final SplashscreenContentDrawer mSplashscreenContentDrawer; - // TODO(b/131727939) remove this when clearing ActivityRecord - private static final int REMOVE_WHEN_TIMEOUT = 2000; - public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor, TransactionPool pool) { mContext = context; @@ -295,12 +292,8 @@ public class StartingSurfaceDrawer { TaskSnapshot snapshot) { final int taskId = startingWindowInfo.taskInfo.taskId; final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken, - snapshot, mSplashScreenExecutor, - () -> removeWindowNoAnimate(taskId)); - mSplashScreenExecutor.executeDelayed(() -> removeWindowNoAnimate(taskId), - REMOVE_WHEN_TIMEOUT); - final StartingWindowRecord tView = - new StartingWindowRecord(null/* decorView */, surface); + snapshot, mSplashScreenExecutor, () -> removeWindowNoAnimate(taskId)); + final StartingWindowRecord tView = new StartingWindowRecord(null/* decorView */, surface); mStartingWindowRecords.put(taskId, tView); } @@ -354,8 +347,6 @@ public class StartingSurfaceDrawer { } if (shouldSaveView) { removeWindowNoAnimate(taskId); - mSplashScreenExecutor.executeDelayed( - () -> removeWindowNoAnimate(taskId), REMOVE_WHEN_TIMEOUT); saveSplashScreenRecord(taskId, view); } return shouldSaveView; @@ -392,7 +383,6 @@ public class StartingSurfaceDrawer { if (leash != null || playRevealAnimation) { mSplashscreenContentDrawer.applyExitAnimation(record.mContentView, leash, frame, record.isEarlyExit(), exitFinish); - mSplashScreenExecutor.executeDelayed(exitFinish, REMOVE_WHEN_TIMEOUT); } else { // the SplashScreenView has been copied to client, skip default exit // animation diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index b21276f5401b..bd5fe2bcbdad 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -16,11 +16,10 @@ package com.android.wm.shell.onehanded; -import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; @@ -67,6 +66,8 @@ public class OneHandedControllerTest extends OneHandedTestCase { @Mock OneHandedGestureHandler mMockGestureHandler; @Mock + OneHandedSettingsUtil mMockSettingsUitl; + @Mock OneHandedUiEventLogger mMockUiEventLogger; @Mock IOverlayManager mMockOverlayManager; @@ -79,13 +80,9 @@ public class OneHandedControllerTest extends OneHandedTestCase { @Mock Handler mMockShellMainHandler; - final boolean mDefaultEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled( - getTestContext().getContentResolver()); - final boolean mDefaultSwipeToNotificationEnabled = - OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( - getTestContext().getContentResolver()); - final boolean mDefaultTapAppToExitEnabled = OneHandedSettingsUtil.getSettingsTapsAppToExit( - getTestContext().getContentResolver()); + final boolean mDefaultEnabled = true; + final boolean mDefaultSwipeToNotificationEnabled = false; + final boolean mDefaultTapAppToExitEnabled = true; @Before public void setUp() { @@ -97,6 +94,14 @@ public class OneHandedControllerTest extends OneHandedTestCase { when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false); when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>()); when(mMockBackgroundOrganizer.getBackgroundSurface()).thenReturn(mMockLeash); + when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any())).thenReturn( + mDefaultEnabled); + when(mMockSettingsUitl.getSettingsOneHandedModeTimeout(any())).thenReturn( + OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS); + when(mMockSettingsUitl.getSettingsTapsAppToExit(any())).thenReturn( + mDefaultTapAppToExitEnabled); + when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any())).thenReturn( + mDefaultSwipeToNotificationEnabled); mSpiedOneHandedController = spy(new OneHandedController( mContext, @@ -107,6 +112,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { mMockTouchHandler, mMockTutorialHandler, mMockGestureHandler, + mMockSettingsUitl, mSpiedTimeoutHandler, mMockUiEventLogger, mMockOverlayManager, @@ -128,15 +134,6 @@ public class OneHandedControllerTest extends OneHandedTestCase { } @Test - public void testNoRegisterAndUnregisterInSameCall() { - if (mDefaultEnabled) { - verify(mMockDisplayAreaOrganizer, never()).unregisterOrganizer(); - } else { - verify(mMockDisplayAreaOrganizer, never()).registerOrganizer(FEATURE_ONE_HANDED); - } - } - - @Test public void testStartOneHandedShouldTriggerScheduleOffset() { when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false); mSpiedOneHandedController.setOneHandedEnabled(true); @@ -190,35 +187,39 @@ public class OneHandedControllerTest extends OneHandedTestCase { public void testUpdateEnabled() { mSpiedOneHandedController.setOneHandedEnabled(true); - verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(mDefaultEnabled); - verify(mMockGestureHandler, atLeastOnce()).onOneHandedEnabled( - mDefaultEnabled || mDefaultSwipeToNotificationEnabled); + verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean()); + verify(mMockGestureHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean()); } @Test public void testUpdateSwipeToNotification() { mSpiedOneHandedController.setSwipeToNotificationEnabled(mDefaultSwipeToNotificationEnabled); - verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(mDefaultEnabled); - verify(mMockGestureHandler, atLeastOnce()).onOneHandedEnabled( - mDefaultEnabled || mDefaultSwipeToNotificationEnabled); + verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean()); + verify(mMockGestureHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean()); } @Test - public void testSettingsObserverUpdateTapAppToExit() { - mSpiedOneHandedController.onTaskChangeExitSettingChanged(); - if (mDefaultTapAppToExitEnabled) { - verify(mMockTaskStackListener, atLeastOnce()).addListener(any()); - } else { - verify(mMockTaskStackListener, atLeastOnce()).removeListener(any()); - } + public void testTapAppToExitEnabledAddListener() { + mSpiedOneHandedController.setTaskChangeToExit(mDefaultTapAppToExitEnabled); + + // If device settings default ON, then addListener() will be trigger 1 time at init + verify(mMockTaskStackListener, atLeastOnce()).addListener(any()); + } + + @Test + public void testTapAppToExitDisabledRemoveListener() { + mSpiedOneHandedController.setTaskChangeToExit(!mDefaultTapAppToExitEnabled); + + // If device settings default ON, then removeListener() will be trigger 1 time at init + verify(mMockTaskStackListener, atLeastOnce()).removeListener(any()); } @Test public void testSettingsObserverUpdateEnabled() { mSpiedOneHandedController.onEnabledSettingChanged(); - verify(mSpiedOneHandedController).setOneHandedEnabled(mDefaultEnabled); + verify(mSpiedOneHandedController).setOneHandedEnabled(anyBoolean()); } @Test @@ -232,14 +233,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { public void testSettingsObserverUpdateSwipeToNotification() { mSpiedOneHandedController.onSwipeToNotificationEnabledSettingChanged(); - // Swipe to notification function is opposite with one handed mode function - if (mDefaultSwipeToNotificationEnabled) { - verify(mSpiedOneHandedController).setSwipeToNotificationEnabled( - mDefaultSwipeToNotificationEnabled); - } else { - verify(mSpiedOneHandedController, never()).setSwipeToNotificationEnabled( - mDefaultSwipeToNotificationEnabled); - } + verify(mSpiedOneHandedController).setSwipeToNotificationEnabled(anyBoolean()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java index 61643d86c8d9..1e6c41af4397 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java @@ -16,17 +16,11 @@ package com.android.wm.shell.onehanded; -import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_LONG_IN_SECONDS; -import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS; -import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER; -import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS; - -import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; import android.content.ContentResolver; import android.database.ContentObserver; -import android.net.Uri; -import android.provider.Settings; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; @@ -34,76 +28,30 @@ import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) public class OneHandedSettingsUtilTest extends OneHandedTestCase { - ContentResolver mContentResolver; - ContentObserver mContentObserver; - boolean mOnChanged; + OneHandedSettingsUtil mSettingsUtil; + + @Mock + ContentResolver mMockContentResolver; + @Mock + ContentObserver mMockContentObserver; @Before public void setUp() { - mContentResolver = mContext.getContentResolver(); - mContentObserver = new ContentObserver(mContext.getMainThreadHandler()) { - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - mOnChanged = true; - } - }; - } - - @Test - public void testRegisterSecureKeyObserver() { - final Uri result = OneHandedSettingsUtil.registerSettingsKeyObserver( - Settings.Secure.TAPS_APP_TO_EXIT, mContentResolver, mContentObserver); + MockitoAnnotations.initMocks(this); - assertThat(result).isNotNull(); - - OneHandedSettingsUtil.registerSettingsKeyObserver( - Settings.Secure.TAPS_APP_TO_EXIT, mContentResolver, mContentObserver); + mSettingsUtil = new OneHandedSettingsUtil(); } @Test public void testUnregisterSecureKeyObserver() { - OneHandedSettingsUtil.registerSettingsKeyObserver( - Settings.Secure.TAPS_APP_TO_EXIT, mContentResolver, mContentObserver); - OneHandedSettingsUtil.unregisterSettingsKeyObserver(mContentResolver, mContentObserver); - - assertThat(mOnChanged).isFalse(); - - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.TAPS_APP_TO_EXIT, 0); - - assertThat(mOnChanged).isFalse(); - } + mSettingsUtil.unregisterSettingsKeyObserver(mMockContentResolver, mMockContentObserver); - @Test - public void testGetSettingsIsOneHandedModeEnabled() { - assertThat(OneHandedSettingsUtil.getSettingsOneHandedModeEnabled( - mContentResolver)).isAnyOf(true, false); - } - - @Test - public void testGetSettingsTapsAppToExit() { - assertThat(OneHandedSettingsUtil.getSettingsTapsAppToExit( - mContentResolver)).isAnyOf(true, false); - } - - @Test - public void testGetSettingsOneHandedModeTimeout() { - assertThat(OneHandedSettingsUtil.getSettingsOneHandedModeTimeout( - mContentResolver)).isAnyOf( - ONE_HANDED_TIMEOUT_NEVER, - ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS, - ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS, - ONE_HANDED_TIMEOUT_LONG_IN_SECONDS); - } - - @Test - public void testGetSettingsSwipeToNotificationEnabled() { - assertThat(OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( - mContentResolver)).isAnyOf(true, false); + verify(mMockContentResolver).unregisterContentObserver(any()); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java index 69c537c2efbe..f586dda145d7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java @@ -64,6 +64,8 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { Handler mMockShellMainHandler; @Mock OneHandedUiEventLogger mMockUiEventLogger; + @Mock + OneHandedSettingsUtil mMockSettingsUtil; @Before public void setUp() { @@ -80,6 +82,7 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { mMockTouchHandler, mMockTutorialHandler, mMockGestureHandler, + mMockSettingsUtil, mTimeoutHandler, mMockUiEventLogger, mMockOverlayManager, diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 971a53a8b2dc..e58f31fd15eb 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -126,7 +126,7 @@ bool Properties::load() { SkAndroidFrameworkTraceUtil::setEnableTracing( base::GetBoolProperty(PROPERTY_SKIA_ATRACE_ENABLED, false)); - runningInEmulator = base::GetBoolProperty(PROPERTY_QEMU_KERNEL, false); + runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false); return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw); } diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index dcb79babad24..42aa87b492e2 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -160,7 +160,7 @@ enum DebugLevel { /** * Property for whether this is running in the emulator. */ -#define PROPERTY_QEMU_KERNEL "ro.kernel.qemu" +#define PROPERTY_IS_EMULATOR "ro.boot.qemu" /////////////////////////////////////////////////////////////////////////////// // Misc diff --git a/location/java/android/location/GnssNavigationMessage.java b/location/java/android/location/GnssNavigationMessage.java index 84a363d25c63..dbf26214066e 100644 --- a/location/java/android/location/GnssNavigationMessage.java +++ b/location/java/android/location/GnssNavigationMessage.java @@ -363,10 +363,10 @@ public final class GnssNavigationMessage implements Parcelable { * <p>The bytes (or words) specified using big endian format (MSB first). * * <ul> - * <li>For GPS L1 C/A, Beidou D1 & Beidou D2, each subframe contains 10 30-bit words. Each - * word (30 bits) should be fit into the last 30 bits in a 4-byte word (skip B31 and B32), with - * MSB first, for a total of 40 bytes, covering a time period of 6, 6, and 0.6 seconds, - * respectively.</li> + * <li>For GPS L1 C/A, IRNSS L5 C/A, Beidou D1 & Beidou D2, each subframe contains 10 + * 30-bit words. Each word (30 bits) should be fit into the last 30 bits in a 4-byte word (skip + * B31 and B32), with MSB first, for a total of 40 bytes, covering a time period of 6, 6, and + * 0.6 seconds, respectively.</li> * <li>For Glonass L1 C/A, each string contains 85 data bits, including the checksum. These * bits should be fit into 11 bytes, with MSB first (skip B86-B88), covering a time period of 2 * seconds.</li> diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index e8e263147c6e..9e9d02e4eebf 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -5287,7 +5287,10 @@ public class AudioManager { * otherwise (typically one device, except for duplicated paths). */ @SystemApi - @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @RequiresPermission(anyOf = { + android.Manifest.permission.MODIFY_AUDIO_ROUTING, + android.Manifest.permission.QUERY_AUDIO_STATE + }) public @NonNull List<AudioDeviceAttributes> getDevicesForAttributes( @NonNull AudioAttributes attributes) { Objects.requireNonNull(attributes); @@ -5426,7 +5429,10 @@ public class AudioManager { * @return the volume behavior for the device */ @SystemApi - @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @RequiresPermission(anyOf = { + android.Manifest.permission.MODIFY_AUDIO_ROUTING, + android.Manifest.permission.QUERY_AUDIO_STATE + }) public @DeviceVolumeBehavior int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) { // verify arguments (validity of device type is enforced in server) @@ -5440,6 +5446,28 @@ public class AudioManager { } } + /** + * @hide + * Returns {@code true} if the volume device behavior is {@link #DEVICE_VOLUME_BEHAVIOR_FULL}. + */ + @TestApi + @RequiresPermission(anyOf = { + android.Manifest.permission.MODIFY_AUDIO_ROUTING, + android.Manifest.permission.QUERY_AUDIO_STATE + }) + public boolean isFullVolumeDevice() { + final AudioAttributes attributes = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .build(); + final List<AudioDeviceAttributes> devices = getDevicesForAttributes(attributes); + for (AudioDeviceAttributes device : devices) { + if (getDeviceVolumeBehavior(device) == DEVICE_VOLUME_BEHAVIOR_FULL) { + return true; + } + } + return false; + } + /** * Indicate wired accessory connection state change. * @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx) diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index f8a642a68dd7..4e8a273ecc9e 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -161,6 +161,10 @@ public final class MediaFormat { public static final String MIMETYPE_AUDIO_EAC3_JOC = "audio/eac3-joc"; public static final String MIMETYPE_AUDIO_AC4 = "audio/ac4"; public static final String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled"; + /** MIME type for MPEG-H Audio single stream */ + public static final String MIMETYPE_AUDIO_MPEGH_MHA1 = "audio/mha1"; + /** MIME type for MPEG-H Audio single stream, encapsulated in MHAS */ + public static final String MIMETYPE_AUDIO_MPEGH_MHM1 = "audio/mhm1"; /** * MIME type for HEIF still image data encoded in HEVC. diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp index 49c2b39f8904..309d71cfd062 100644 --- a/media/jni/soundpool/StreamManager.cpp +++ b/media/jni/soundpool/StreamManager.cpp @@ -358,14 +358,14 @@ void StreamManager::addToActiveQueue_l(Stream *stream) { void StreamManager::run(int32_t id) { ALOGV("%s(%d) entering", __func__, id); - int64_t waitTimeNs = kWaitTimeBeforeCloseNs; + int64_t waitTimeNs = 0; // on thread start, mRestartStreams can be non-empty. std::unique_lock lock(mStreamManagerLock); while (!mQuit) { - if (mRestartStreams.empty()) { // on thread start, mRestartStreams can be non-empty. + if (waitTimeNs > 0) { mStreamManagerCondition.wait_for( lock, std::chrono::duration<int64_t, std::nano>(waitTimeNs)); } - ALOGV("%s(%d) awake", __func__, id); + ALOGV("%s(%d) awake lock waitTimeNs:%lld", __func__, id, (long long)waitTimeNs); sanityCheckQueue_l(); diff --git a/packages/CompanionDeviceManager/res/values-af/strings.xml b/packages/CompanionDeviceManager/res/values-af/strings.xml index ed120b53d636..36e59ba832cc 100644 --- a/packages/CompanionDeviceManager/res/values-af/strings.xml +++ b/packages/CompanionDeviceManager/res/values-af/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Kies \'n <xliff:g id="PROFILE_NAME">%1$s</xliff:g> om deur <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> bestuur te word"</string> <string name="profile_name_generic" msgid="6851028682723034988">"toestel"</string> <string name="profile_name_watch" msgid="576290739483672360">"horlosie"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Stel <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> om jou <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> te bestuur"</string> + <string name="profile_summary" msgid="2059360676631420073">"Hierdie program is nodig om jou <xliff:g id="PROFILE_NAME">%1$s</xliff:g> te bestuur. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Laat toe"</string> + <string name="consent_no" msgid="2640796915611404382">"Moenie toelaat nie"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-am/strings.xml b/packages/CompanionDeviceManager/res/values-am/strings.xml index 76f68e7ee495..0fefa8af15de 100644 --- a/packages/CompanionDeviceManager/res/values-am/strings.xml +++ b/packages/CompanionDeviceManager/res/values-am/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"በ<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> የሚተዳደር <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ይምረጡ"</string> <string name="profile_name_generic" msgid="6851028682723034988">"መሣሪያ"</string> <string name="profile_name_watch" msgid="576290739483672360">"ሰዓት"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> የእርስዎን <xliff:g id="DEVICE_NAME">%2$s</xliff:g> - <strong></strong> ለማስተዳደር"</string> + <string name="profile_summary" msgid="2059360676631420073">"የእርስዎን <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ለማስተዳደር ይህ መተግበሪያ ያስፈልጋል <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"ፍቀድ"</string> + <string name="consent_no" msgid="2640796915611404382">"አትፍቀድ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ar/strings.xml b/packages/CompanionDeviceManager/res/values-ar/strings.xml index 92783a529cfc..ca3b9f364c76 100644 --- a/packages/CompanionDeviceManager/res/values-ar/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ar/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"اختَر <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ليديره تطبيق <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"جهاز"</string> <string name="profile_name_watch" msgid="576290739483672360">"ساعة"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"ضبط <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> لإدارة <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"هذا التطبيق مطلوب لإدارة <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"السماح"</string> + <string name="consent_no" msgid="2640796915611404382">"عدم السماح"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-az/strings.xml b/packages/CompanionDeviceManager/res/values-az/strings.xml index b07cad615d57..8e4a202b18ea 100644 --- a/packages/CompanionDeviceManager/res/values-az/strings.xml +++ b/packages/CompanionDeviceManager/res/values-az/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> tərəfindən idarə ediləcək <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string> <string name="profile_name_generic" msgid="6851028682723034988">"cihaz"</string> <string name="profile_name_watch" msgid="576290739483672360">"izləyin"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> cihazınızı idarə etmək üçün <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ayarlayın"</string> + <string name="profile_summary" msgid="2059360676631420073">"Bu tətbiq <xliff:g id="PROFILE_NAME">%1$s</xliff:g> profilinizi idarə etmək üçün lazımdır. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"İcazə verin"</string> + <string name="consent_no" msgid="2640796915611404382">"İcazə verməyin"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml index edeaa7785e36..ef19c48207f3 100644 --- a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml +++ b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Odaberite profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string> <string name="profile_name_watch" msgid="576290739483672360">"sat"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Podesite aplikaciju <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> tako da upravlja uređajem <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Ova aplikacija je potrebna za upravljanje profilom <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Dozvoli"</string> + <string name="consent_no" msgid="2640796915611404382">"Ne dozvoli"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-be/strings.xml b/packages/CompanionDeviceManager/res/values-be/strings.xml index 9410d686133b..4366a0895222 100644 --- a/packages/CompanionDeviceManager/res/values-be/strings.xml +++ b/packages/CompanionDeviceManager/res/values-be/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Выберыце прыладу (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), якой будзе кіраваць праграма <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"прылада"</string> <string name="profile_name_watch" msgid="576290739483672360">"гадзіннік"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Дазвольце праграме <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> кіраваць прыладай <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Гэта праграма неабходная для кіравання профілем \"<xliff:g id="PROFILE_NAME">%1$s</xliff:g>\". <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Дазволіць"</string> + <string name="consent_no" msgid="2640796915611404382">"Не дазваляць"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-bg/strings.xml b/packages/CompanionDeviceManager/res/values-bg/strings.xml index 4457dbd2cb3a..77f3413f8b88 100644 --- a/packages/CompanionDeviceManager/res/values-bg/strings.xml +++ b/packages/CompanionDeviceManager/res/values-bg/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Изберете устройство (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), което да се управлява от <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"устройство"</string> <string name="profile_name_watch" msgid="576290739483672360">"часовник"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Задайте <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> да управлява устройството ви <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Това приложение е необходимо за управление на <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Разрешаване"</string> + <string name="consent_no" msgid="2640796915611404382">"Забраняване"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-bn/strings.xml b/packages/CompanionDeviceManager/res/values-bn/strings.xml index 4aa7e74e5432..5fa47813dfe2 100644 --- a/packages/CompanionDeviceManager/res/values-bn/strings.xml +++ b/packages/CompanionDeviceManager/res/values-bn/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> বেছে নিন যেটি <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ম্যানেজ করবে"</string> <string name="profile_name_generic" msgid="6851028682723034988">"ডিভাইস"</string> <string name="profile_name_watch" msgid="576290739483672360">"দেখুন"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"আপনার <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> ম্যানেজ করার জন্য <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> সেট করুন"</string> + <string name="profile_summary" msgid="2059360676631420073">"আপনার <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ম্যানেজ করতে এই অ্যাপটি প্রয়োজন। <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"অনুমতি দিন"</string> + <string name="consent_no" msgid="2640796915611404382">"অনুমতি দেবেন না"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-bs/strings.xml b/packages/CompanionDeviceManager/res/values-bs/strings.xml index 8ffa3272fb8c..18153b589372 100644 --- a/packages/CompanionDeviceManager/res/values-bs/strings.xml +++ b/packages/CompanionDeviceManager/res/values-bs/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Odaberite uređaj <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string> <string name="profile_name_watch" msgid="576290739483672360">"sat"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Postavite aplikaciju <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> da upravlja vašim uređajem <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Ova aplikacija je potrebna za upravljanje profilom: <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Dozvoli"</string> + <string name="consent_no" msgid="2640796915611404382">"Nemoj dozvoliti"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ca/strings.xml b/packages/CompanionDeviceManager/res/values-ca/strings.xml index c9c186eba741..bbd1125eef6d 100644 --- a/packages/CompanionDeviceManager/res/values-ca/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ca/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Tria un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> perquè el gestioni <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"dispositiu"</string> <string name="profile_name_watch" msgid="576290739483672360">"rellotge"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Defineix que <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> gestioni el dispositiu <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Aquesta aplicació es necessita per gestionar <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Permet"</string> + <string name="consent_no" msgid="2640796915611404382">"No permetis"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-cs/strings.xml b/packages/CompanionDeviceManager/res/values-cs/strings.xml index b2ac32db2a99..c5fed3372030 100644 --- a/packages/CompanionDeviceManager/res/values-cs/strings.xml +++ b/packages/CompanionDeviceManager/res/values-cs/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Vyberte zařízení <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, které chcete spravovat pomocí aplikace <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"zařízení"</string> <string name="profile_name_watch" msgid="576290739483672360">"hodinky"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Nastavit aplikaci <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> pro správu vašeho zařízení: <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Tato aplikace je nutná pro správu profilu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Povolit"</string> + <string name="consent_no" msgid="2640796915611404382">"Nepovolovat"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-da/strings.xml b/packages/CompanionDeviceManager/res/values-da/strings.xml index bc67948c3ecf..21280d527b43 100644 --- a/packages/CompanionDeviceManager/res/values-da/strings.xml +++ b/packages/CompanionDeviceManager/res/values-da/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Vælg den enhed (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), som skal administreres af <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"enhed"</string> <string name="profile_name_watch" msgid="576290739483672360">"ur"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Indstil <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> til at administrere <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Du skal bruge denne app for at administrere dit <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Tillad"</string> + <string name="consent_no" msgid="2640796915611404382">"Tillad ikke"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-el/strings.xml b/packages/CompanionDeviceManager/res/values-el/strings.xml index fed516fb04eb..162d6fc8b32d 100644 --- a/packages/CompanionDeviceManager/res/values-el/strings.xml +++ b/packages/CompanionDeviceManager/res/values-el/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Επιλέξτε ένα προφίλ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> για διαχείριση από την εφαρμογή <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"συσκευή"</string> <string name="profile_name_watch" msgid="576290739483672360">"ρολόι"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Ορίστε την εφαρμογή <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> για διαχείριση της συσκευής <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Αυτή η εφαρμογή είναι απαραίτητη για τη διαχείριση του προφίλ σας <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Να επιτρέπεται"</string> + <string name="consent_no" msgid="2640796915611404382">"Να μην επιτρέπεται"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml index 4fd057f3e7e4..de89b39a37dd 100644 --- a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml +++ b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"device"</string> <string name="profile_name_watch" msgid="576290739483672360">"watch"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Set <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> to manage your <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"This app is needed to manage your <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Allow"</string> + <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml index 4fd057f3e7e4..de89b39a37dd 100644 --- a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml +++ b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"device"</string> <string name="profile_name_watch" msgid="576290739483672360">"watch"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Set <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> to manage your <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"This app is needed to manage your <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Allow"</string> + <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml index 4fd057f3e7e4..de89b39a37dd 100644 --- a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml +++ b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"device"</string> <string name="profile_name_watch" msgid="576290739483672360">"watch"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Set <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> to manage your <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"This app is needed to manage your <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Allow"</string> + <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml index 4fd057f3e7e4..de89b39a37dd 100644 --- a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml +++ b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"device"</string> <string name="profile_name_watch" msgid="576290739483672360">"watch"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Set <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> to manage your <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"This app is needed to manage your <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Allow"</string> + <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml index 0e3902c50aea..10c20121883b 100644 --- a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml +++ b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml @@ -22,10 +22,7 @@ <string name="profile_name_watch" msgid="576290739483672360">"watch"</string> <!-- no translation found for confirmation_title (814973816731238955) --> <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="profile_summary" msgid="2059360676631420073">"This app is needed to manage your <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Allow"</string> + <string name="consent_no" msgid="2640796915611404382">"Don’t allow"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml index 6b903c609fe9..8dae51c706b8 100644 --- a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml +++ b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para que <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> lo administre"</string> <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string> <string name="profile_name_watch" msgid="576290739483672360">"reloj"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Configura <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para administrar el dispositivo <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Esta app es necesaria para administrar tu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string> + <string name="consent_no" msgid="2640796915611404382">"No permitir"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-es/strings.xml b/packages/CompanionDeviceManager/res/values-es/strings.xml index 0a2906a97a39..a307b61a44f9 100644 --- a/packages/CompanionDeviceManager/res/values-es/strings.xml +++ b/packages/CompanionDeviceManager/res/values-es/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para gestionarlo con <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string> <string name="profile_name_watch" msgid="576290739483672360">"reloj"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Configura <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para que gestione tu dispositivo <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Se necesita esta aplicación para gestionar tu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string> + <string name="consent_no" msgid="2640796915611404382">"No permitir"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-et/strings.xml b/packages/CompanionDeviceManager/res/values-et/strings.xml index 4958a17206f7..2fb2bc49daff 100644 --- a/packages/CompanionDeviceManager/res/values-et/strings.xml +++ b/packages/CompanionDeviceManager/res/values-et/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Valige seade <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, mida haldab rakendus <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"seade"</string> <string name="profile_name_watch" msgid="576290739483672360">"käekell"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Määrake rakendus <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> haldama teie seadet <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Seda rakendust on vaja teie profiili <xliff:g id="PROFILE_NAME">%1$s</xliff:g> haldamiseks. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Luba"</string> + <string name="consent_no" msgid="2640796915611404382">"Ära luba"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-eu/strings.xml b/packages/CompanionDeviceManager/res/values-eu/strings.xml index 2a61fd519511..646f844ac572 100644 --- a/packages/CompanionDeviceManager/res/values-eu/strings.xml +++ b/packages/CompanionDeviceManager/res/values-eu/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Aukeratu <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> aplikazioak kudeatu beharreko <xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"gailua"</string> <string name="profile_name_watch" msgid="576290739483672360">"erlojua"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Konfiguratu <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> kudea dezan"</string> + <string name="profile_summary" msgid="2059360676631420073">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> kudeatzeko beharrezkoa da aplikazioa. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Eman baimena"</string> + <string name="consent_no" msgid="2640796915611404382">"Ez eman baimenik"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-fa/strings.xml b/packages/CompanionDeviceManager/res/values-fa/strings.xml index 8d102303d91a..69b9647a07dd 100644 --- a/packages/CompanionDeviceManager/res/values-fa/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fa/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"انتخاب <xliff:g id="PROFILE_NAME">%1$s</xliff:g> برای مدیریت کردن با <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"دستگاه"</string> <string name="profile_name_watch" msgid="576290739483672360">"ساعت"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"تنظیم <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> برای مدیریت <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"این برنامه برای مدیریت <xliff:g id="PROFILE_NAME">%1$s</xliff:g> شما لازم است. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"مجاز"</string> + <string name="consent_no" msgid="2640796915611404382">"مجاز نیست"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-fi/strings.xml b/packages/CompanionDeviceManager/res/values-fi/strings.xml index cfa03af870dc..b174b8a9d5bb 100644 --- a/packages/CompanionDeviceManager/res/values-fi/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fi/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Valitse <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, jota <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> hallinnoi"</string> <string name="profile_name_generic" msgid="6851028682723034988">"laite"</string> <string name="profile_name_watch" msgid="576290739483672360">"kello"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Valitse <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> laitteen (<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>) ylläpitäjäksi"</string> + <string name="profile_summary" msgid="2059360676631420073">"Profiilin (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) ylläpitoon tarvitaan tätä sovellusta. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Salli"</string> + <string name="consent_no" msgid="2640796915611404382">"Älä salli"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml index d3cfc299c4ce..696598d9f4b9 100644 --- a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Choisissez un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré par <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string> <string name="profile_name_watch" msgid="576290739483672360">"montre"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Utiliser <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> pour gérer votre <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Cette application est nécessaire pour gérer votre <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string> + <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-fr/strings.xml b/packages/CompanionDeviceManager/res/values-fr/strings.xml index d82e3905c5d1..787794bf3893 100644 --- a/packages/CompanionDeviceManager/res/values-fr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fr/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Sélectionner le/la <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré(e) par <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string> <string name="profile_name_watch" msgid="576290739483672360">"montre"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Définir <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> pour gérer votre <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Cette appli est nécessaire pour gérer votre <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string> + <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-gl/strings.xml b/packages/CompanionDeviceManager/res/values-gl/strings.xml index 2a85398cf3cc..a3efc4c0610d 100644 --- a/packages/CompanionDeviceManager/res/values-gl/strings.xml +++ b/packages/CompanionDeviceManager/res/values-gl/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Escolle un perfil (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) para que o xestione a aplicación <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string> <string name="profile_name_watch" msgid="576290739483672360">"reloxo"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Configura <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para que xestione o teu dispositivo <strong>(<xliff:g id="DEVICE_NAME">%2$s</xliff:g>)</strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Esta aplicación é necesaria para xestionar o teu perfil (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>). <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string> + <string name="consent_no" msgid="2640796915611404382">"Non permitir"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-gu/strings.xml b/packages/CompanionDeviceManager/res/values-gu/strings.xml index 6f52403ed416..1b0fe1ac2efc 100644 --- a/packages/CompanionDeviceManager/res/values-gu/strings.xml +++ b/packages/CompanionDeviceManager/res/values-gu/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> દ્વારા મેનેજ કરવા માટે કોઈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> પસંદ કરો"</string> <string name="profile_name_generic" msgid="6851028682723034988">"ડિવાઇસ"</string> <string name="profile_name_watch" msgid="576290739483672360">"સ્માર્ટવૉચ"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"તમારું <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> મેનેજ કરવા માટે, <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> સેટ કરો"</string> + <string name="profile_summary" msgid="2059360676631420073">"તમારી <xliff:g id="PROFILE_NAME">%1$s</xliff:g> મેનેજ કરવા માટે આ ઍપ જરૂરી છે. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"મંજૂરી આપો"</string> + <string name="consent_no" msgid="2640796915611404382">"મંજૂરી આપશો નહીં"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-hi/strings.xml b/packages/CompanionDeviceManager/res/values-hi/strings.xml index 9af99924c76a..dee30f259562 100644 --- a/packages/CompanionDeviceManager/res/values-hi/strings.xml +++ b/packages/CompanionDeviceManager/res/values-hi/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"कोई <xliff:g id="PROFILE_NAME">%1$s</xliff:g> चुनें, ताकि उसे <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> की मदद से प्रबंधित किया जा सके"</string> <string name="profile_name_generic" msgid="6851028682723034988">"डिवाइस"</string> <string name="profile_name_watch" msgid="576290739483672360">"स्मार्टवॉच"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> को मैनेज करने के लिए, <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> सेट अप करें"</string> + <string name="profile_summary" msgid="2059360676631420073">"यह ऐप्लिकेशन, <xliff:g id="PROFILE_NAME">%1$s</xliff:g> मैनेज करने के लिए ज़रूरी है. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"अनुमति दें"</string> + <string name="consent_no" msgid="2640796915611404382">"अनुमति न दें"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-hr/strings.xml b/packages/CompanionDeviceManager/res/values-hr/strings.xml index fe30ec485169..bc36521dd1e1 100644 --- a/packages/CompanionDeviceManager/res/values-hr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-hr/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Odaberite profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string> <string name="profile_name_watch" msgid="576290739483672360">"satom"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Postavite aplikaciju <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> da upravlja vašim uređajem <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Ta je aplikacija potrebna za upravljanje vašim profilom <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Dopusti"</string> + <string name="consent_no" msgid="2640796915611404382">"Nemoj dopustiti"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-hu/strings.xml b/packages/CompanionDeviceManager/res/values-hu/strings.xml index 370d4dff6a98..ef505443ffee 100644 --- a/packages/CompanionDeviceManager/res/values-hu/strings.xml +++ b/packages/CompanionDeviceManager/res/values-hu/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"A(z) <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> alkalmazással kezelni kívánt <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiválasztása"</string> <string name="profile_name_generic" msgid="6851028682723034988">"eszköz"</string> <string name="profile_name_watch" msgid="576290739483672360">"óra"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"A(z) <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> alkalmazás beállítva a(z) <xliff:g id="DEVICE_NAME">%2$s</xliff:g> (<strong></strong>) kezelésére"</string> + <string name="profile_summary" msgid="2059360676631420073">"Szükség van erre az alkalmazásra a következő kezeléséhez: <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Engedélyezés"</string> + <string name="consent_no" msgid="2640796915611404382">"Tiltás"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-hy/strings.xml b/packages/CompanionDeviceManager/res/values-hy/strings.xml index fee55d0c5396..103361aa9cfa 100644 --- a/packages/CompanionDeviceManager/res/values-hy/strings.xml +++ b/packages/CompanionDeviceManager/res/values-hy/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Ընտրեք <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ը, որը պետք է կառավարվի <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> հավելվածի կողմից"</string> <string name="profile_name_generic" msgid="6851028682723034988">"սարք"</string> <string name="profile_name_watch" msgid="576290739483672360">"ժամացույց"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Ընտրեք <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> հավելվածը որպես <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> սարքի կառավարիչ"</string> + <string name="profile_summary" msgid="2059360676631420073">"Այս հավելվածն անհրաժեշտ է ձեր <xliff:g id="PROFILE_NAME">%1$s</xliff:g> պրոֆիլը կառավարելու համար։ <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Թույլատրել"</string> + <string name="consent_no" msgid="2640796915611404382">"Չթույլատրել"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-in/strings.xml b/packages/CompanionDeviceManager/res/values-in/strings.xml index 498bf2cd3351..225b276a608d 100644 --- a/packages/CompanionDeviceManager/res/values-in/strings.xml +++ b/packages/CompanionDeviceManager/res/values-in/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk dikelola oleh <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"perangkat"</string> <string name="profile_name_watch" msgid="576290739483672360">"smartwatch"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Tetapkan <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> untuk mengelola <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Aplikasi ini diperlukan untuk mengelola <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Izinkan"</string> + <string name="consent_no" msgid="2640796915611404382">"Jangan izinkan"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-is/strings.xml b/packages/CompanionDeviceManager/res/values-is/strings.xml index bd12658284ef..7855a2ac4240 100644 --- a/packages/CompanionDeviceManager/res/values-is/strings.xml +++ b/packages/CompanionDeviceManager/res/values-is/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Velja <xliff:g id="PROFILE_NAME">%1$s</xliff:g> sem <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> á að stjórna"</string> <string name="profile_name_generic" msgid="6851028682723034988">"tæki"</string> <string name="profile_name_watch" msgid="576290739483672360">"úr"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Veita <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> stjórn á <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Þetta forrit er nauðsynlegt til að hafa umsjón með <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Leyfa"</string> + <string name="consent_no" msgid="2640796915611404382">"Ekki leyfa"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-it/strings.xml b/packages/CompanionDeviceManager/res/values-it/strings.xml index 40d43207bced..9e503e16e71d 100644 --- a/packages/CompanionDeviceManager/res/values-it/strings.xml +++ b/packages/CompanionDeviceManager/res/values-it/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Scegli un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> che sia gestito da <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string> <string name="profile_name_watch" msgid="576290739483672360">"orologio"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Configura l\'app <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> affinché gestisca <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Questa app è necessaria per gestire il tuo <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Consenti"</string> + <string name="consent_no" msgid="2640796915611404382">"Non consentire"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-iw/strings.xml b/packages/CompanionDeviceManager/res/values-iw/strings.xml index 807cdd471642..d293fb0f6c18 100644 --- a/packages/CompanionDeviceManager/res/values-iw/strings.xml +++ b/packages/CompanionDeviceManager/res/values-iw/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"בחירה של <xliff:g id="PROFILE_NAME">%1$s</xliff:g> לניהול באמצעות <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"מכשיר"</string> <string name="profile_name_watch" msgid="576290739483672360">"שעון"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"הגדרה של <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> לניהול <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"האפליקציה הזו נחוצה כדי לנהל את <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"יש אישור"</string> + <string name="consent_no" msgid="2640796915611404382">"אין אישור"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ja/strings.xml b/packages/CompanionDeviceManager/res/values-ja/strings.xml index 92022be9fade..ac8e2d2d0698 100644 --- a/packages/CompanionDeviceManager/res/values-ja/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ja/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> の管理対象となる<xliff:g id="PROFILE_NAME">%1$s</xliff:g>の選択"</string> <string name="profile_name_generic" msgid="6851028682723034988">"デバイス"</string> <string name="profile_name_watch" msgid="576290739483672360">"ウォッチ"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> を管理する <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> の設定"</string> + <string name="profile_summary" msgid="2059360676631420073">"このアプリは<xliff:g id="PROFILE_NAME">%1$s</xliff:g>の管理に必要です。<xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"許可"</string> + <string name="consent_no" msgid="2640796915611404382">"許可しない"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ka/strings.xml b/packages/CompanionDeviceManager/res/values-ka/strings.xml index 64a79b48ec7e..8b7680ee34c1 100644 --- a/packages/CompanionDeviceManager/res/values-ka/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ka/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"აირჩიეთ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, რომელიც უნდა მართოს <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>-მა"</string> <string name="profile_name_generic" msgid="6851028682723034988">"მოწყობილობა"</string> <string name="profile_name_watch" msgid="576290739483672360">"საათი"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"დააყენეთ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>, რათა მართოთ თქვენი <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"ეს აპი საჭიროა თქვენი <xliff:g id="PROFILE_NAME">%1$s</xliff:g>-ს სამართავად. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"დაშვება"</string> + <string name="consent_no" msgid="2640796915611404382">"არ დაიშვას"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-kk/strings.xml b/packages/CompanionDeviceManager/res/values-kk/strings.xml index edd5c0ee9cd0..1ad854e3087b 100644 --- a/packages/CompanionDeviceManager/res/values-kk/strings.xml +++ b/packages/CompanionDeviceManager/res/values-kk/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> арқылы басқарылатын <xliff:g id="PROFILE_NAME">%1$s</xliff:g> құрылғысын таңдаңыз"</string> <string name="profile_name_generic" msgid="6851028682723034988">"құрылғы"</string> <string name="profile_name_watch" msgid="576290739483672360">"сағат"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> қолданбасына <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> құрылғысын басқаруға рұқсат беріңіз"</string> + <string name="profile_summary" msgid="2059360676631420073">"Бұл қолданба <xliff:g id="PROFILE_NAME">%1$s</xliff:g> профиліңізді басқару үшін қажет. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Рұқсат беру"</string> + <string name="consent_no" msgid="2640796915611404382">"Рұқсат бермеу"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-km/strings.xml b/packages/CompanionDeviceManager/res/values-km/strings.xml index 36c02de6790d..7231c2d577a8 100644 --- a/packages/CompanionDeviceManager/res/values-km/strings.xml +++ b/packages/CompanionDeviceManager/res/values-km/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"ជ្រើសរើស <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ដើម្បីឱ្យស្ថិតក្រោមការគ្រប់គ្រងរបស់ <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"ឧបករណ៍"</string> <string name="profile_name_watch" msgid="576290739483672360">"នាឡិកា"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"កំណត់ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ដើម្បីគ្រប់គ្រង <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> របស់អ្នក"</string> + <string name="profile_summary" msgid="2059360676631420073">"ត្រូវការកម្មវិធីនេះ ដើម្បីគ្រប់គ្រង <xliff:g id="PROFILE_NAME">%1$s</xliff:g> របស់អ្នក។ <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"អនុញ្ញាត"</string> + <string name="consent_no" msgid="2640796915611404382">"កុំអនុញ្ញាត"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-kn/strings.xml b/packages/CompanionDeviceManager/res/values-kn/strings.xml index 56c1557e5fef..6f7532877fb5 100644 --- a/packages/CompanionDeviceManager/res/values-kn/strings.xml +++ b/packages/CompanionDeviceManager/res/values-kn/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ಮೂಲಕ ನಿರ್ವಹಿಸಬೇಕಾದ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string> <string name="profile_name_generic" msgid="6851028682723034988">"ಸಾಧನ"</string> <string name="profile_name_watch" msgid="576290739483672360">"ವೀಕ್ಷಿಸಿ"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"ನಿಮ್ಮ <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> ಅನ್ನು ನಿರ್ವಹಿಸಲು <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ಅನ್ನು ಸೆಟ್ ಮಾಡಿ"</string> + <string name="profile_summary" msgid="2059360676631420073">"ನಿಮ್ಮ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. ಅನ್ನು ನಿರ್ವಹಿಸಲು ಈ ಆ್ಯಪ್ನ ಅಗತ್ಯವಿದೆ. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"ಅನುಮತಿಸಿ"</string> + <string name="consent_no" msgid="2640796915611404382">"ಅನುಮತಿಸಬೇಡಿ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ko/strings.xml b/packages/CompanionDeviceManager/res/values-ko/strings.xml index 79c36dd017fa..5b171eaded63 100644 --- a/packages/CompanionDeviceManager/res/values-ko/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ko/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>에서 관리할 <xliff:g id="PROFILE_NAME">%1$s</xliff:g>을(를) 선택"</string> <string name="profile_name_generic" msgid="6851028682723034988">"기기"</string> <string name="profile_name_watch" msgid="576290739483672360">"시계"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> 앱이 <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> 기기를 관리하도록 설정"</string> + <string name="profile_summary" msgid="2059360676631420073">"이 앱은 <xliff:g id="PROFILE_NAME">%1$s</xliff:g> 프로필을 관리하는 데 필요합니다. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"허용"</string> + <string name="consent_no" msgid="2640796915611404382">"허용 안함"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml index 8a90b3dc7d6a..f7c896be7f98 100644 --- a/packages/CompanionDeviceManager/res/values-ky/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> тарабынан башкарылсын"</string> <string name="profile_name_generic" msgid="6851028682723034988">"түзмөк"</string> <string name="profile_name_watch" msgid="576290739483672360">"саат"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> түзмөгүңүздү башкаруу үчүн <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> колдонмосун жөндөңүз"</string> + <string name="profile_summary" msgid="2059360676631420073">"Бул колдонмо <xliff:g id="PROFILE_NAME">%1$s</xliff:g> профилиңизди башкаруу үчүн керек. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Уруксат берүү"</string> + <string name="consent_no" msgid="2640796915611404382">"Уруксат берилбесин"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-lo/strings.xml b/packages/CompanionDeviceManager/res/values-lo/strings.xml index a6564b347651..8ad881f63e8b 100644 --- a/packages/CompanionDeviceManager/res/values-lo/strings.xml +++ b/packages/CompanionDeviceManager/res/values-lo/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"ເລືອກ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ເພື່ອໃຫ້ຖືກຈັດການໂດຍ <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"ອຸປະກອນ"</string> <string name="profile_name_watch" msgid="576290739483672360">"ໂມງ"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"ຕັ້ງຄ່າ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ເພື່ອຈັດການ <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> ຂອງທ່ານ"</string> + <string name="profile_summary" msgid="2059360676631420073">"ຕ້ອງໃຊ້ແອັບນີ້ເພື່ອຈັດການ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ຂອງທ່ານ. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"ອະນຸຍາດ"</string> + <string name="consent_no" msgid="2640796915611404382">"ບໍ່ອະນຸຍາດ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-lt/strings.xml b/packages/CompanionDeviceManager/res/values-lt/strings.xml index 382f1cf69a2d..c40d4a75753f 100644 --- a/packages/CompanionDeviceManager/res/values-lt/strings.xml +++ b/packages/CompanionDeviceManager/res/values-lt/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Jūsų <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, kurį valdys <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> (pasirinkite)"</string> <string name="profile_name_generic" msgid="6851028682723034988">"įrenginys"</string> <string name="profile_name_watch" msgid="576290739483672360">"laikrodis"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> tvarkymo naudojant <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> nustatymas"</string> + <string name="profile_summary" msgid="2059360676631420073">"Ši programa reikalinga norint tvarkyti jūsų <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Leisti"</string> + <string name="consent_no" msgid="2640796915611404382">"Neleisti"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-lv/strings.xml b/packages/CompanionDeviceManager/res/values-lv/strings.xml index 8d70bf788e27..c842ee1436e3 100644 --- a/packages/CompanionDeviceManager/res/values-lv/strings.xml +++ b/packages/CompanionDeviceManager/res/values-lv/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Profila (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) izvēle, ko pārvaldīt lietotnē <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"ierīce"</string> <string name="profile_name_watch" msgid="576290739483672360">"pulkstenis"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Lietotnes <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> iestatīšana ierīces <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> pārvaldībai"</string> + <string name="profile_summary" msgid="2059360676631420073">"Šī lietotne ir nepieciešama jūsu profila (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) pārvaldībai. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Atļaut"</string> + <string name="consent_no" msgid="2640796915611404382">"Neatļaut"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-mk/strings.xml b/packages/CompanionDeviceManager/res/values-mk/strings.xml index 5322e98c37b7..fdc366e1900f 100644 --- a/packages/CompanionDeviceManager/res/values-mk/strings.xml +++ b/packages/CompanionDeviceManager/res/values-mk/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Изберете <xliff:g id="PROFILE_NAME">%1$s</xliff:g> со којшто ќе управува <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"уред"</string> <string name="profile_name_watch" msgid="576290739483672360">"часовник"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Поставете <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> за да управувате со вашиот <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Апликацијава е потребна за управување со вашиот <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Дозволи"</string> + <string name="consent_no" msgid="2640796915611404382">"Не дозволувај"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ml/strings.xml b/packages/CompanionDeviceManager/res/values-ml/strings.xml index a9262c7973fb..ae445a3f0f12 100644 --- a/packages/CompanionDeviceManager/res/values-ml/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ml/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ഉപയോഗിച്ച് മാനേജ് ചെയ്യുന്നതിന് ഒരു <xliff:g id="PROFILE_NAME">%1$s</xliff:g> തിരഞ്ഞെടുക്കുക"</string> <string name="profile_name_generic" msgid="6851028682723034988">"ഉപകരണം"</string> <string name="profile_name_watch" msgid="576290739483672360">"വാച്ച്"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"നിങ്ങളുടെ <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> മാനേജ് ചെയ്യുന്നതിന് <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> സജ്ജീകരിക്കുക"</string> + <string name="profile_summary" msgid="2059360676631420073">"നിങ്ങളുടെ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> മാനേജ് ചെയ്യാൻ ഈ ആപ്പ് ആവശ്യമാണ്. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"അനുവദിക്കുക"</string> + <string name="consent_no" msgid="2640796915611404382">"അനുവദിക്കരുത്"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-mn/strings.xml b/packages/CompanionDeviceManager/res/values-mn/strings.xml index 303286466f4b..01850e707fb4 100644 --- a/packages/CompanionDeviceManager/res/values-mn/strings.xml +++ b/packages/CompanionDeviceManager/res/values-mn/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>-н удирдах<xliff:g id="PROFILE_NAME">%1$s</xliff:g>-г сонгоно уу"</string> <string name="profile_name_generic" msgid="6851028682723034988">"төхөөрөмж"</string> <string name="profile_name_watch" msgid="576290739483672360">"цаг"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Та <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>-г удирдахын тулд <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>-г тохируулна уу"</string> + <string name="profile_summary" msgid="2059360676631420073">"Энэ апп таны <xliff:g id="PROFILE_NAME">%1$s</xliff:g>-г удирдахад шаардлагатай. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Зөвшөөрөх"</string> + <string name="consent_no" msgid="2640796915611404382">"Бүү зөвшөөр"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-mr/strings.xml b/packages/CompanionDeviceManager/res/values-mr/strings.xml index 01dae7d3a21e..7eea9bf0941a 100644 --- a/packages/CompanionDeviceManager/res/values-mr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-mr/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> द्वारे व्यवस्थापित करण्यासाठी <xliff:g id="PROFILE_NAME">%1$s</xliff:g> निवडा"</string> <string name="profile_name_generic" msgid="6851028682723034988">"डिव्हाइस"</string> <string name="profile_name_watch" msgid="576290739483672360">"पाहा"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"तुमचे <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> व्यवस्थापित करण्यासाठी <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> सेट करा"</string> + <string name="profile_summary" msgid="2059360676631420073">"तुमची <xliff:g id="PROFILE_NAME">%1$s</xliff:g> प्रोफाइल व्यवस्थापित करण्यासाठी हे ॲप आवश्यक आहे. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"अनुमती द्या"</string> + <string name="consent_no" msgid="2640796915611404382">"अनुमती देऊ नका"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ms/strings.xml b/packages/CompanionDeviceManager/res/values-ms/strings.xml index 4e0f58bc2653..e43a27f0cecf 100644 --- a/packages/CompanionDeviceManager/res/values-ms/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ms/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk diurus oleh <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"peranti"</string> <string name="profile_name_watch" msgid="576290739483672360">"jam tangan"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Tetapkan <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> untuk mengurus <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> anda"</string> + <string name="profile_summary" msgid="2059360676631420073">"Apl ini diperlukan untuk menguruskan <xliff:g id="PROFILE_NAME">%1$s</xliff:g> anda. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Benarkan"</string> + <string name="consent_no" msgid="2640796915611404382">"Jangan benarkan"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-my/strings.xml b/packages/CompanionDeviceManager/res/values-my/strings.xml index 050c8ce4364a..1bd3a1d9f9e7 100644 --- a/packages/CompanionDeviceManager/res/values-my/strings.xml +++ b/packages/CompanionDeviceManager/res/values-my/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> က စီမံခန့်ခွဲရန် <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ကို ရွေးချယ်ပါ"</string> <string name="profile_name_generic" msgid="6851028682723034988">"စက်"</string> <string name="profile_name_watch" msgid="576290739483672360">"နာရီ"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"သင်၏ <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> ကို စီမံခန့်ခွဲရန် <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ကို သတ်မှတ်ပါ"</string> + <string name="profile_summary" msgid="2059360676631420073">"သင်၏ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်ကိုလိုအပ်သည်။ <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"ခွင့်ပြုရန်"</string> + <string name="consent_no" msgid="2640796915611404382">"ခွင့်မပြုပါ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-nb/strings.xml b/packages/CompanionDeviceManager/res/values-nb/strings.xml index a8e22033faed..e4d247b4ae00 100644 --- a/packages/CompanionDeviceManager/res/values-nb/strings.xml +++ b/packages/CompanionDeviceManager/res/values-nb/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Velg <xliff:g id="PROFILE_NAME">%1$s</xliff:g> som skal administreres av <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"enhet"</string> <string name="profile_name_watch" msgid="576290739483672360">"klokke"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Velg at <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> skal administrere <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Denne appen kreves for å administrere <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Tillat"</string> + <string name="consent_no" msgid="2640796915611404382">"Ikke tillat"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-nl/strings.xml b/packages/CompanionDeviceManager/res/values-nl/strings.xml index e7e03904843e..9dc23e7ee750 100644 --- a/packages/CompanionDeviceManager/res/values-nl/strings.xml +++ b/packages/CompanionDeviceManager/res/values-nl/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Een <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiezen om te beheren met <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"apparaat"</string> <string name="profile_name_watch" msgid="576290739483672360">"horloge"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> instellen om je <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> te beheren"</string> + <string name="profile_summary" msgid="2059360676631420073">"Deze app is vereist om je <xliff:g id="PROFILE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Toestaan"</string> + <string name="consent_no" msgid="2640796915611404382">"Niet toestaan"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-or/strings.xml b/packages/CompanionDeviceManager/res/values-or/strings.xml index 1f516fa4c0d0..4cfe0573dd42 100644 --- a/packages/CompanionDeviceManager/res/values-or/strings.xml +++ b/packages/CompanionDeviceManager/res/values-or/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ଦ୍ୱାରା ପରିଚାଳିତ ହେବା ପାଇଁ ଏକ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>କୁ ବାଛନ୍ତୁ"</string> <string name="profile_name_generic" msgid="6851028682723034988">"ଡିଭାଇସ୍"</string> <string name="profile_name_watch" msgid="576290739483672360">"ୱାଚ୍"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"ଆପଣଙ୍କ <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>କୁ ପରିଚାଳନା କରିବା ପାଇଁ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>କୁ ସେଟ୍ କରନ୍ତୁ"</string> + <string name="profile_summary" msgid="2059360676631420073">"ଆପଣଙ୍କ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>କୁ ପରିଚାଳନା କରିବା ପାଇଁ ଏହି ଆପ୍ ଆବଶ୍ୟକ। <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"ଅନୁମତି ଦିଅନ୍ତୁ"</string> + <string name="consent_no" msgid="2640796915611404382">"ଅନୁମତି ଦିଅନ୍ତୁ ନାହିଁ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-pl/strings.xml b/packages/CompanionDeviceManager/res/values-pl/strings.xml index 7adf064e80b6..3dbd2f75a68d 100644 --- a/packages/CompanionDeviceManager/res/values-pl/strings.xml +++ b/packages/CompanionDeviceManager/res/values-pl/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Wybierz profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, którym ma zarządzać aplikacja <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"urządzenie"</string> <string name="profile_name_watch" msgid="576290739483672360">"zegarek"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Skonfiguruj aplikację <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>, aby zarządzała urządzeniem <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Ta aplikacja jest niezbędna do zarządzania profilem <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Zezwól"</string> + <string name="consent_no" msgid="2640796915611404382">"Nie zezwalaj"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml index b5ddc6db6f78..91a9fa4a58b6 100644 --- a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml +++ b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string> <string name="profile_name_watch" msgid="576290739483672360">"relógio"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Configure o app <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para gerenciar seu <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Esse app é necessário para gerenciar seu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string> + <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml index c06ac7de1c11..5ad9389f82e2 100644 --- a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml +++ b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerido pela app <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string> <string name="profile_name_watch" msgid="576290739483672360">"relógio"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Defina a app <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para gerir o seu <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Esta app é necessária para gerir o seu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string> + <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-pt/strings.xml b/packages/CompanionDeviceManager/res/values-pt/strings.xml index b5ddc6db6f78..91a9fa4a58b6 100644 --- a/packages/CompanionDeviceManager/res/values-pt/strings.xml +++ b/packages/CompanionDeviceManager/res/values-pt/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string> <string name="profile_name_watch" msgid="576290739483672360">"relógio"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Configure o app <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para gerenciar seu <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Esse app é necessário para gerenciar seu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string> + <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ro/strings.xml b/packages/CompanionDeviceManager/res/values-ro/strings.xml index 437e8dc78c76..15f5393e7c75 100644 --- a/packages/CompanionDeviceManager/res/values-ro/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ro/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Alegeți un profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> pe care să îl gestioneze <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"dispozitiv"</string> <string name="profile_name_watch" msgid="576290739483672360">"ceas"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Setați <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> pentru a vă gestiona dispozitivul <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Această aplicație este necesară pentru a gestiona <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Permiteți"</string> + <string name="consent_no" msgid="2640796915611404382">"Nu permiteți"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ru/strings.xml b/packages/CompanionDeviceManager/res/values-ru/strings.xml index d087959971b6..2f79416ffc4b 100644 --- a/packages/CompanionDeviceManager/res/values-ru/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ru/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Выберите устройство (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), которым будет управлять приложение <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"устройство"</string> <string name="profile_name_watch" msgid="576290739483672360">"часы"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Разрешите приложению <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> управлять устройством <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Это приложение необходимо для управления вашим профилем (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>). <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Разрешить"</string> + <string name="consent_no" msgid="2640796915611404382">"Запретить"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-si/strings.xml b/packages/CompanionDeviceManager/res/values-si/strings.xml index 365b6bfb3d2a..d108a25ce2da 100644 --- a/packages/CompanionDeviceManager/res/values-si/strings.xml +++ b/packages/CompanionDeviceManager/res/values-si/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> මගින් කළමනාකරණය කරනු ලැබීමට <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ක් තෝරන්න"</string> <string name="profile_name_generic" msgid="6851028682723034988">"උපාංගය"</string> <string name="profile_name_watch" msgid="576290739483672360">"ඔරලෝසුව"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ඔබගේ <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> කළමනාකරණය කිරීමට සකසන්න"</string> + <string name="profile_summary" msgid="2059360676631420073">"මෙම යෙදුමට ඔබගේ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> කළමනාකරණය කිරීමට අවශ්යයි. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"ඉඩ දෙන්න"</string> + <string name="consent_no" msgid="2640796915611404382">"ඉඩ නොදෙන්න"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-sk/strings.xml b/packages/CompanionDeviceManager/res/values-sk/strings.xml index d5c099a36424..d53728b34d5a 100644 --- a/packages/CompanionDeviceManager/res/values-sk/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sk/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Vyberte profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, ktorý bude spravovať aplikácia <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"zariadenie"</string> <string name="profile_name_watch" msgid="576290739483672360">"hodinky"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Nastavte aplikáciu <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>, aby spravovala zariadenie <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Táto aplikácia sa vyžaduje na správu profilu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Povoliť"</string> + <string name="consent_no" msgid="2640796915611404382">"Nepovoliť"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-sl/strings.xml b/packages/CompanionDeviceManager/res/values-sl/strings.xml index d855db1cea72..28492105f8f2 100644 --- a/packages/CompanionDeviceManager/res/values-sl/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sl/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Izbira naprave <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, ki jo bo upravljala aplikacija <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"naprava"</string> <string name="profile_name_watch" msgid="576290739483672360">"ura"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Nastavitev aplikacije <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> za upravljanje naprave <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Ta aplikacija je potrebna za upravljanje profila »<xliff:g id="PROFILE_NAME">%1$s</xliff:g>«. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Dovoli"</string> + <string name="consent_no" msgid="2640796915611404382">"Ne dovoli"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-sq/strings.xml b/packages/CompanionDeviceManager/res/values-sq/strings.xml index 7335446765da..a57835a1d92e 100644 --- a/packages/CompanionDeviceManager/res/values-sq/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sq/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Zgjidh një profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> që do të menaxhohet nga <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"pajisja"</string> <string name="profile_name_watch" msgid="576290739483672360">"ora inteligjente"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Cakto <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> që të menaxhojë pajisjen tënde <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Ky aplikacion nevojitet për të menaxhuar profilin tënd <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Lejo"</string> + <string name="consent_no" msgid="2640796915611404382">"Mos lejo"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-sr/strings.xml b/packages/CompanionDeviceManager/res/values-sr/strings.xml index f3f65e17101b..0b01cbce9a12 100644 --- a/packages/CompanionDeviceManager/res/values-sr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sr/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Одаберите профил <xliff:g id="PROFILE_NAME">%1$s</xliff:g> којим ће управљати апликација <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"уређај"</string> <string name="profile_name_watch" msgid="576290739483672360">"сат"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Подесите апликацију <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> тако да управља уређајем <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Ова апликација је потребна за управљање профилом <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Дозволи"</string> + <string name="consent_no" msgid="2640796915611404382">"Не дозволи"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-sv/strings.xml b/packages/CompanionDeviceManager/res/values-sv/strings.xml index 574166822006..f6dcec9054c7 100644 --- a/packages/CompanionDeviceManager/res/values-sv/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sv/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Välj en <xliff:g id="PROFILE_NAME">%1$s</xliff:g> för hantering av <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"enhet"</string> <string name="profile_name_watch" msgid="576290739483672360">"klocka"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Konfigurera <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> för att hantera din <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Appen behövs för att hantera <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Tillåt"</string> + <string name="consent_no" msgid="2640796915611404382">"Tillåt inte"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml index 357eb4a475b8..0999c5b87635 100644 --- a/packages/CompanionDeviceManager/res/values-sw/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Chagua <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ili idhibitiwe na <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"kifaa"</string> <string name="profile_name_watch" msgid="576290739483672360">"saa"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Weka <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ili udhibiti <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> yako"</string> + <string name="profile_summary" msgid="2059360676631420073">"Programu hii inahitajika ili udhibiti wasifu wako wa <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Ruhusu"</string> + <string name="consent_no" msgid="2640796915611404382">"Usiruhusu"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ta/strings.xml b/packages/CompanionDeviceManager/res/values-ta/strings.xml index 651d9e276a7a..884d57f5b61b 100644 --- a/packages/CompanionDeviceManager/res/values-ta/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ta/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ஆப்ஸ் நிர்வகிக்கக்கூடிய <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ஐத் தேர்ந்தெடுங்கள்"</string> <string name="profile_name_generic" msgid="6851028682723034988">"சாதனம்"</string> <string name="profile_name_watch" msgid="576290739483672360">"வாட்ச்"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"உங்கள் <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> சாதனத்தை நிர்வகிக்க <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ஆப்ஸை அமையுங்கள்"</string> + <string name="profile_summary" msgid="2059360676631420073">"உங்கள் <xliff:g id="PROFILE_NAME">%1$s</xliff:g> சுயவிவரத்தை நிர்வகிக்க இந்த ஆப்ஸ் தேவைப்படுகிறது. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"அனுமதி"</string> + <string name="consent_no" msgid="2640796915611404382">"அனுமதிக்க வேண்டாம்"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-th/strings.xml b/packages/CompanionDeviceManager/res/values-th/strings.xml index 557515af6ac8..58ebda67e685 100644 --- a/packages/CompanionDeviceManager/res/values-th/strings.xml +++ b/packages/CompanionDeviceManager/res/values-th/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"เลือก<xliff:g id="PROFILE_NAME">%1$s</xliff:g>ที่จะให้มีการจัดการโดย <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"อุปกรณ์"</string> <string name="profile_name_watch" msgid="576290739483672360">"นาฬิกา"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"ตั้งค่าให้ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> จัดการ <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> ของคุณ"</string> + <string name="profile_summary" msgid="2059360676631420073">"ต้องใช้แอปนี้ในการจัดการ<xliff:g id="PROFILE_NAME">%1$s</xliff:g>ของคุณ <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"อนุญาต"</string> + <string name="consent_no" msgid="2640796915611404382">"ไม่อนุญาต"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-tl/strings.xml b/packages/CompanionDeviceManager/res/values-tl/strings.xml index 6cbf2e875881..1a8328464b3e 100644 --- a/packages/CompanionDeviceManager/res/values-tl/strings.xml +++ b/packages/CompanionDeviceManager/res/values-tl/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Pumili ng <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para pamahalaan ng <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"device"</string> <string name="profile_name_watch" msgid="576290739483672360">"relo"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Itakda ang <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para pamahalaan ang iyong <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Kinakailangan ang app na ito para pamahalaan ang iyong <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Payagan"</string> + <string name="consent_no" msgid="2640796915611404382">"Huwag payagan"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-tr/strings.xml b/packages/CompanionDeviceManager/res/values-tr/strings.xml index bc1ab31d9b90..b89e8748e04e 100644 --- a/packages/CompanionDeviceManager/res/values-tr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-tr/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> tarafından yönetilecek bir <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string> <string name="profile_name_generic" msgid="6851028682723034988">"cihaz"</string> <string name="profile_name_watch" msgid="576290739483672360">"saat"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> cihazınızı yönetmek için <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> uygulamasını ayarlayın"</string> + <string name="profile_summary" msgid="2059360676631420073">"Bu uygulama, <xliff:g id="PROFILE_NAME">%1$s</xliff:g> profilinizin yönetilmesi için gereklidir. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"İzin ver"</string> + <string name="consent_no" msgid="2640796915611404382">"İzin verme"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-uk/strings.xml b/packages/CompanionDeviceManager/res/values-uk/strings.xml index 33477c8f1429..2ae852cb7c3f 100644 --- a/packages/CompanionDeviceManager/res/values-uk/strings.xml +++ b/packages/CompanionDeviceManager/res/values-uk/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Виберіть <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, яким керуватиме додаток <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"пристрій"</string> <string name="profile_name_watch" msgid="576290739483672360">"годинник"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Налаштуйте додаток <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>, щоб керувати пристроєм <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"Цей додаток потрібен, щоб керувати профілем \"<xliff:g id="PROFILE_NAME">%1$s</xliff:g>\". <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Дозволити"</string> + <string name="consent_no" msgid="2640796915611404382">"Не дозволяти"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-uz/strings.xml b/packages/CompanionDeviceManager/res/values-uz/strings.xml index b16db730def4..5681118c6998 100644 --- a/packages/CompanionDeviceManager/res/values-uz/strings.xml +++ b/packages/CompanionDeviceManager/res/values-uz/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> boshqaradigan <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qurilmasini tanlang"</string> <string name="profile_name_generic" msgid="6851028682723034988">"qurilma"</string> <string name="profile_name_watch" msgid="576290739483672360">"soat"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> qurilmasini boshqarish uchun <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ilovasini sozlang"</string> + <string name="profile_summary" msgid="2059360676631420073">"Bu ilova <xliff:g id="PROFILE_NAME">%1$s</xliff:g> profilini boshqarish uchun kerak. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Ruxsat"</string> + <string name="consent_no" msgid="2640796915611404382">"Ruxsat berilmasin"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-vi/strings.xml b/packages/CompanionDeviceManager/res/values-vi/strings.xml index 75d2de37cb14..3bbdb57a227c 100644 --- a/packages/CompanionDeviceManager/res/values-vi/strings.xml +++ b/packages/CompanionDeviceManager/res/values-vi/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Chọn một <xliff:g id="PROFILE_NAME">%1$s</xliff:g> sẽ do <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> quản lý"</string> <string name="profile_name_generic" msgid="6851028682723034988">"thiết bị"</string> <string name="profile_name_watch" msgid="576290739483672360">"đồng hồ"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Đặt <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> để quản lý <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> của bạn"</string> + <string name="profile_summary" msgid="2059360676631420073">"Cần có ứng dụng này để quản lý <xliff:g id="PROFILE_NAME">%1$s</xliff:g> của bạn. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Cho phép"</string> + <string name="consent_no" msgid="2640796915611404382">"Không cho phép"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml index 576f3f46e8ba..be29925b72e2 100644 --- a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml +++ b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"選擇由 <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> 管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"裝置"</string> <string name="profile_name_watch" msgid="576290739483672360">"手錶"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"設定 <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> 來管理您的 <xliff:g id="DEVICE_NAME">%2$s</xliff:g> - <strong></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"必須使用此應用程式,才能管理<xliff:g id="PROFILE_NAME">%1$s</xliff:g>。<xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"允許"</string> + <string name="consent_no" msgid="2640796915611404382">"不允許"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml index 8a50658ad25c..80448690cec9 100644 --- a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml +++ b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"選擇要讓「<xliff:g id="APP_NAME">%2$s</xliff:g>」<strong></strong>管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"裝置"</string> <string name="profile_name_watch" msgid="576290739483672360">"手錶"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」<strong></strong>管理你的「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」<strong></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"你必須使用這個應用程式,才能管理<xliff:g id="PROFILE_NAME">%1$s</xliff:g>。<xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"允許"</string> + <string name="consent_no" msgid="2640796915611404382">"不允許"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-zu/strings.xml b/packages/CompanionDeviceManager/res/values-zu/strings.xml index fc3f19d72ee2..3ed177cd6e08 100644 --- a/packages/CompanionDeviceManager/res/values-zu/strings.xml +++ b/packages/CompanionDeviceManager/res/values-zu/strings.xml @@ -20,12 +20,8 @@ <string name="chooser_title" msgid="2262294130493605839">"Khetha i-<xliff:g id="PROFILE_NAME">%1$s</xliff:g> ezophathwa yi-<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"idivayisi"</string> <string name="profile_name_watch" msgid="576290739483672360">"buka"</string> - <!-- no translation found for confirmation_title (814973816731238955) --> - <skip /> - <!-- no translation found for profile_summary (2059360676631420073) --> - <skip /> - <!-- no translation found for consent_yes (8344487259618762872) --> - <skip /> - <!-- no translation found for consent_no (2640796915611404382) --> - <skip /> + <string name="confirmation_title" msgid="814973816731238955">"Setha i-<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ukuze iphathe i-<xliff:g id="DEVICE_NAME">%2$s</xliff:g> yakho - <strong></strong>"</string> + <string name="profile_summary" msgid="2059360676631420073">"I-app iyadingeka ukuphatha i-<xliff:g id="PROFILE_NAME">%1$s</xliff:g> yakho. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> + <string name="consent_yes" msgid="8344487259618762872">"Vumela"</string> + <string name="consent_no" msgid="2640796915611404382">"Ungavumeli"</string> </resources> diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp index 86b85e8398ce..86433e1c38f8 100644 --- a/packages/Connectivity/framework/Android.bp +++ b/packages/Connectivity/framework/Android.bp @@ -83,3 +83,38 @@ java_sdk_library { ], permitted_packages: ["android.net", "com.android.connectivity.aidl"], } + +java_library { + name: "framework-connectivity.impl", + // Instead of building against private API (framework.jar), + // build against core_platform + framework-minus-apex + module + // stub libs. This allows framework.jar to depend on this library, + // so it can be part of the private API until all clients have been migrated. + // TODO: just build against module_api, and remove this jar from + // the private API. + sdk_version: "core_platform", + srcs: [ + ":framework-connectivity-sources", + ], + aidl: { + include_dirs: [ + "frameworks/base/core/java", // For framework parcelables + "frameworks/native/aidl/binder", // For PersistableBundle.aidl + ], + }, + libs: [ + "framework-minus-apex", + // TODO: just framework-tethering, framework-wifi when building against module_api + "framework-tethering.stubs.module_lib", + "framework-wifi.stubs.module_lib", + "unsupportedappusage", + "ServiceConnectivityResources", + ], + static_libs: [ + "net-utils-device-common", + ], + jarjar_rules: "jarjar-rules.txt", + apex_available: ["com.android.tethering"], + installable: true, + permitted_packages: ["android.net", "com.android.connectivity.aidl"], +} diff --git a/packages/Connectivity/framework/api/current.txt b/packages/Connectivity/framework/api/current.txt index 243e4ca4295a..f22d4b7b779a 100644 --- a/packages/Connectivity/framework/api/current.txt +++ b/packages/Connectivity/framework/api/current.txt @@ -87,6 +87,7 @@ package android.net { method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public boolean isActiveNetworkMetered(); method public boolean isDefaultNetworkActive(); method @Deprecated public static boolean isNetworkTypeValid(int); + method public void registerBestMatchingNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback); diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt index b9bcddba5d0a..bb296476c72f 100644 --- a/packages/Connectivity/framework/api/module-lib-current.txt +++ b/packages/Connectivity/framework/api/module-lib-current.txt @@ -11,6 +11,7 @@ package android.net { method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); field public static final String PRIVATE_DNS_MODE_OFF = "off"; field public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; @@ -29,6 +30,11 @@ package android.net { field public static final int TRANSPORT_TEST = 7; // 0x7 } + public class ParseException extends java.lang.RuntimeException { + ctor public ParseException(@NonNull String); + ctor public ParseException(@NonNull String, @NonNull Throwable); + } + public final class TcpRepairWindow { ctor public TcpRepairWindow(int, int, int, int, int, int); field public final int maxWindow; diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt index a98f14ea9408..4dca411cca24 100644 --- a/packages/Connectivity/framework/api/system-current.txt +++ b/packages/Connectivity/framework/api/system-current.txt @@ -56,7 +56,7 @@ package android.net { method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); - method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener); + method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable Runnable); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi(); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle); method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); @@ -67,6 +67,8 @@ package android.net { method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback); field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; + field public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; // 0x0 + field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1 field public static final int TETHERING_BLUETOOTH = 2; // 0x2 field public static final int TETHERING_USB = 1; // 0x1 field public static final int TETHERING_WIFI = 0; // 0x0 @@ -78,10 +80,6 @@ package android.net { field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd } - public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener { - method public void onComplete(); - } - @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback { ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback(); method @Deprecated public void onTetheringFailed(); diff --git a/packages/Connectivity/framework/jarjar-rules.txt b/packages/Connectivity/framework/jarjar-rules.txt new file mode 100644 index 000000000000..381a4ac87505 --- /dev/null +++ b/packages/Connectivity/framework/jarjar-rules.txt @@ -0,0 +1,7 @@ +rule com.android.net.module.util.** android.net.connectivity.framework.util.@1 + +# TODO (b/149403767): remove the annotations from net-utils-device-common instead of here +zap android.annotation.** +zap com.android.net.module.annotation.** +zap com.android.internal.annotations.** + diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java index 6d6a5547930c..ba5eb1090dbf 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -18,8 +18,8 @@ package android.net; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST; import static android.net.NetworkRequest.Type.LISTEN; +import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST; import static android.net.NetworkRequest.Type.REQUEST; -import static android.net.NetworkRequest.Type.TRACK_BEST; import static android.net.NetworkRequest.Type.TRACK_DEFAULT; import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT; import static android.net.QosCallback.QosCallbackRegistrationException; @@ -64,6 +64,7 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ServiceSpecificException; +import android.os.UserHandle; import android.provider.Settings; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -76,7 +77,6 @@ import android.util.SparseIntArray; import com.android.connectivity.aidl.INetworkAgent; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; -import com.android.internal.util.Protocol; import libcore.net.event.NetworkEventDispatcher; @@ -971,6 +971,33 @@ public class ConnectivityManager { } /** + * Preference for {@link #setNetworkPreferenceForUser(UserHandle, int, Executor, Runnable)}. + * Specify that the traffic for this user should by follow the default rules. + * @hide + */ + @SystemApi + public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; + + /** + * Preference for {@link #setNetworkPreferenceForUser(UserHandle, int, Executor, Runnable)}. + * Specify that the traffic for this user should by default go on a network with + * {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE}, and on the system default network + * if no such network is available. + * @hide + */ + @SystemApi + public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + PROFILE_NETWORK_PREFERENCE_DEFAULT, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE + }) + public @interface ProfileNetworkPreference { + } + + /** * Specifies the preferred network type. When the device has more * than one type available the preferred network type will be used. * @@ -3213,23 +3240,6 @@ public class ConnectivityManager { } } - // TODO : remove this method. It is a stopgap measure to help sheperding a number - // of dependent changes that would conflict throughout the automerger graph. Having this - // temporarily helps with the process of going through with all these dependent changes across - // the entire tree. - /** - * @hide - * Register a NetworkAgent with ConnectivityService. - * @return Network corresponding to NetworkAgent. - */ - @RequiresPermission(anyOf = { - NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, - android.Manifest.permission.NETWORK_FACTORY}) - public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp, - NetworkCapabilities nc, int score, NetworkAgentConfig config) { - return registerNetworkAgent(na, ni, lp, nc, score, config, NetworkProvider.ID_NONE); - } - /** * @hide * Register a NetworkAgent with ConnectivityService. @@ -3239,7 +3249,8 @@ public class ConnectivityManager { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp, - NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) { + NetworkCapabilities nc, @NonNull NetworkScore score, NetworkAgentConfig config, + int providerId) { try { return mService.registerNetworkAgent(na, ni, lp, nc, score, config, providerId); } catch (RemoteException e) { @@ -3539,29 +3550,28 @@ public class ConnectivityManager { } } - private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER; /** @hide */ - public static final int CALLBACK_PRECHECK = BASE + 1; + public static final int CALLBACK_PRECHECK = 1; /** @hide */ - public static final int CALLBACK_AVAILABLE = BASE + 2; + public static final int CALLBACK_AVAILABLE = 2; /** @hide arg1 = TTL */ - public static final int CALLBACK_LOSING = BASE + 3; + public static final int CALLBACK_LOSING = 3; /** @hide */ - public static final int CALLBACK_LOST = BASE + 4; + public static final int CALLBACK_LOST = 4; /** @hide */ - public static final int CALLBACK_UNAVAIL = BASE + 5; + public static final int CALLBACK_UNAVAIL = 5; /** @hide */ - public static final int CALLBACK_CAP_CHANGED = BASE + 6; + public static final int CALLBACK_CAP_CHANGED = 6; /** @hide */ - public static final int CALLBACK_IP_CHANGED = BASE + 7; + public static final int CALLBACK_IP_CHANGED = 7; /** @hide obj = NetworkCapabilities, arg1 = seq number */ - private static final int EXPIRE_LEGACY_REQUEST = BASE + 8; + private static final int EXPIRE_LEGACY_REQUEST = 8; /** @hide */ - public static final int CALLBACK_SUSPENDED = BASE + 9; + public static final int CALLBACK_SUSPENDED = 9; /** @hide */ - public static final int CALLBACK_RESUMED = BASE + 10; + public static final int CALLBACK_RESUMED = 10; /** @hide */ - public static final int CALLBACK_BLK_CHANGED = BASE + 11; + public static final int CALLBACK_BLK_CHANGED = 11; /** @hide */ public static String getCallbackName(int whichCallback) { @@ -4257,15 +4267,33 @@ public class ConnectivityManager { } /** - * @hide + * Registers to receive notifications about the best matching network which satisfy the given + * {@link NetworkRequest}. The callbacks will continue to be called until + * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is + * called. + * + * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * {@link #registerNetworkCallback} and its variants and {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} that the system will call as suitable + * networks change state. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. */ - // TODO: Make it public api. @SuppressLint("ExecutorRegistration") public void registerBestMatchingNetworkCallback(@NonNull NetworkRequest request, @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { final NetworkCapabilities nc = request.networkCapabilities; final CallbackHandler cbHandler = new CallbackHandler(handler); - sendRequestForNetwork(nc, networkCallback, 0, TRACK_BEST, TYPE_NONE, cbHandler); + sendRequestForNetwork(nc, networkCallback, 0, LISTEN_FOR_BEST, TYPE_NONE, cbHandler); } /** @@ -5065,19 +5093,6 @@ public class ConnectivityManager { } /** - * Listener for {@link #setOemNetworkPreference(OemNetworkPreferences, Executor, - * OnSetOemNetworkPreferenceListener)}. - * @hide - */ - @SystemApi - public interface OnSetOemNetworkPreferenceListener { - /** - * Called when setOemNetworkPreference() successfully completes. - */ - void onComplete(); - } - - /** * Used by automotive devices to set the network preferences used to direct traffic at an * application level as per the given OemNetworkPreferences. An example use-case would be an * automotive OEM wanting to provide connectivity for applications critical to the usage of a @@ -5099,16 +5114,16 @@ public class ConnectivityManager { @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference, @Nullable @CallbackExecutor final Executor executor, - @Nullable final OnSetOemNetworkPreferenceListener listener) { + @Nullable final Runnable listener) { Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); if (null != listener) { Objects.requireNonNull(executor, "Executor must be non-null"); } - final IOnSetOemNetworkPreferenceListener listenerInternal = listener == null ? null : - new IOnSetOemNetworkPreferenceListener.Stub() { + final IOnCompleteListener listenerInternal = listener == null ? null : + new IOnCompleteListener.Stub() { @Override public void onComplete() { - executor.execute(listener::onComplete); + executor.execute(listener::run); } }; @@ -5120,10 +5135,56 @@ public class ConnectivityManager { } } + /** + * Request that a user profile is put by default on a network matching a given preference. + * + * See the documentation for the individual preferences for a description of the supported + * behaviors. + * + * @param profile the profile concerned. + * @param preference the preference for this profile. + * @param executor an executor to execute the listener on. Optional if listener is null. + * @param listener an optional listener to listen for completion of the operation. + * @throws IllegalArgumentException if {@code profile} is not a valid user profile. + * @throws SecurityException if missing the appropriate permissions. + * @hide + */ + // This function is for establishing per-profile default networking and can only be called by + // the device policy manager, running as the system server. It would make no sense to call it + // on a context for a user because it does not establish a setting on behalf of a user, rather + // it establishes a setting for a user on behalf of the DPM. + @SuppressLint({"UserHandle"}) + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + public void setProfileNetworkPreference(@NonNull final UserHandle profile, + @ProfileNetworkPreference final int preference, + @Nullable @CallbackExecutor final Executor executor, + @Nullable final Runnable listener) { + if (null != listener) { + Objects.requireNonNull(executor, "Pass a non-null executor, or a null listener"); + } + final IOnCompleteListener proxy; + if (null == listener) { + proxy = null; + } else { + proxy = new IOnCompleteListener.Stub() { + @Override + public void onComplete() { + executor.execute(listener::run); + } + }; + } + try { + mService.setProfileNetworkPreference(profile, preference, proxy); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + // The first network ID of IPSec tunnel interface. - private static final int TUN_INTF_NETID_START = 0xFC00; + private static final int TUN_INTF_NETID_START = 0xFC00; // 0xFC00 = 64512 // The network ID range of IPSec tunnel interface. - private static final int TUN_INTF_NETID_RANGE = 0x0400; + private static final int TUN_INTF_NETID_RANGE = 0x0400; // 0x0400 = 1024 /** * Get the network ID range reserved for IPSec tunnel interfaces. @@ -5140,8 +5201,7 @@ public class ConnectivityManager { /** * Get private DNS mode from settings. * - * @param context The Context to get its ContentResolver to query the private DNS mode from - * settings. + * @param context The Context to query the private DNS mode from settings. * @return A string of private DNS mode as one of the PRIVATE_DNS_MODE_* constants. * * @hide diff --git a/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java b/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java new file mode 100644 index 000000000000..d4543654522f --- /dev/null +++ b/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +/** + * A manager class for connectivity module settings. + * + * @hide + */ +public class ConnectivitySettingsManager { + + private ConnectivitySettingsManager() {} + + /** + * Whether to automatically switch away from wifi networks that lose Internet access. + * Only meaningful if config_networkAvoidBadWifi is set to 0, otherwise the system always + * avoids such networks. Valid values are: + * + * 0: Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013. + * null: Ask the user whether to switch away from bad wifi. + * 1: Avoid bad wifi. + */ + public static final String NETWORK_AVOID_BAD_WIFI = "network_avoid_bad_wifi"; + + /** + * User setting for ConnectivityManager.getMeteredMultipathPreference(). This value may be + * overridden by the system based on device or application state. If null, the value + * specified by config_networkMeteredMultipathPreference is used. + */ + public static final String NETWORK_METERED_MULTIPATH_PREFERENCE = + "network_metered_multipath_preference"; +} diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl index f9393e315b83..d83cc163b53f 100644 --- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl +++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl @@ -20,7 +20,7 @@ import android.app.PendingIntent; import android.net.ConnectionInfo; import android.net.ConnectivityDiagnosticsManager; import android.net.IConnectivityDiagnosticsCallback; -import android.net.IOnSetOemNetworkPreferenceListener; +import android.net.IOnCompleteListener; import android.net.INetworkActivityListener; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; @@ -30,6 +30,7 @@ import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkRequest; +import android.net.NetworkScore; import android.net.NetworkState; import android.net.NetworkStateSnapshot; import android.net.OemNetworkPreferences; @@ -42,6 +43,7 @@ import android.os.Messenger; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.ResultReceiver; +import android.os.UserHandle; import com.android.connectivity.aidl.INetworkAgent; @@ -138,7 +140,7 @@ interface IConnectivityManager void declareNetworkRequestUnfulfillable(in NetworkRequest request); Network registerNetworkAgent(in INetworkAgent na, in NetworkInfo ni, in LinkProperties lp, - in NetworkCapabilities nc, int score, in NetworkAgentConfig config, + in NetworkCapabilities nc, in NetworkScore score, in NetworkAgentConfig config, in int factorySerialNumber); NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, int reqType, @@ -214,5 +216,8 @@ interface IConnectivityManager void unregisterQosCallback(in IQosCallback callback); void setOemNetworkPreference(in OemNetworkPreferences preference, - in IOnSetOemNetworkPreferenceListener listener); + in IOnCompleteListener listener); + + void setProfileNetworkPreference(in UserHandle profile, int preference, + in IOnCompleteListener listener); } diff --git a/packages/Connectivity/framework/src/android/net/Network.java b/packages/Connectivity/framework/src/android/net/Network.java index 46141e0d0c1e..7245db3b17db 100644 --- a/packages/Connectivity/framework/src/android/net/Network.java +++ b/packages/Connectivity/framework/src/android/net/Network.java @@ -30,10 +30,10 @@ import android.system.OsConstants; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; -import com.android.okhttp.internalandroidapi.Dns; -import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory; import libcore.io.IoUtils; +import libcore.net.http.Dns; +import libcore.net.http.HttpURLConnectionFactory; import java.io.FileDescriptor; import java.io.IOException; @@ -299,7 +299,7 @@ public class Network implements Parcelable { // Set configuration on the HttpURLConnectionFactory that will be good for all // connections created by this Network. Configuration that might vary is left // until openConnection() and passed as arguments. - HttpURLConnectionFactory urlConnectionFactory = new HttpURLConnectionFactory(); + HttpURLConnectionFactory urlConnectionFactory = HttpURLConnectionFactory.createInstance(); urlConnectionFactory.setDns(dnsLookup); // Let traffic go via dnsLookup // A private connection pool just for this Network. urlConnectionFactory.setNewConnectionPool(httpMaxConnections, diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java index 27aa15d1e1e4..a127c6f6de26 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java +++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java @@ -37,7 +37,6 @@ import android.util.Log; import com.android.connectivity.aidl.INetworkAgent; import com.android.connectivity.aidl.INetworkAgentRegistry; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Protocol; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -125,7 +124,10 @@ public abstract class NetworkAgent { */ public final int providerId; - private static final int BASE = Protocol.BASE_NETWORK_AGENT; + // ConnectivityService parses message constants from itself and NetworkAgent with MessageUtils + // for debugging purposes, and crashes if some messages have the same values. + // TODO: have ConnectivityService store message names in different maps and remove this base + private static final int BASE = 200; /** * Sent by ConnectivityService to the NetworkAgent to inform it of @@ -371,6 +373,14 @@ public abstract class NetworkAgent { return ni; } + // Temporary backward compatibility constructor + public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag, + @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score, + @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) { + this(context, looper, logTag, nc, lp, + new NetworkScore.Builder().setLegacyInt(score).build(), config, provider); + } + /** * Create a new network agent. * @param context a {@link Context} to get system services from. @@ -382,10 +392,12 @@ public abstract class NetworkAgent { * @param score the initial score of this network. Update with sendNetworkScore. * @param config an immutable {@link NetworkAgentConfig} for this agent. * @param provider the {@link NetworkProvider} managing this agent. + * @hide TODO : unhide when impl is complete */ public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag, - @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score, - @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) { + @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, + @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, + @Nullable NetworkProvider provider) { this(looper, context, logTag, nc, lp, score, config, provider == null ? NetworkProvider.ID_NONE : provider.getProviderId(), getLegacyNetworkInfo(config)); @@ -395,12 +407,12 @@ public abstract class NetworkAgent { public final Context context; public final NetworkCapabilities capabilities; public final LinkProperties properties; - public final int score; + public final NetworkScore score; public final NetworkAgentConfig config; public final NetworkInfo info; InitialConfiguration(@NonNull Context context, @NonNull NetworkCapabilities capabilities, - @NonNull LinkProperties properties, int score, @NonNull NetworkAgentConfig config, - @NonNull NetworkInfo info) { + @NonNull LinkProperties properties, @NonNull NetworkScore score, + @NonNull NetworkAgentConfig config, @NonNull NetworkInfo info) { this.context = context; this.capabilities = capabilities; this.properties = properties; @@ -412,8 +424,9 @@ public abstract class NetworkAgent { private volatile InitialConfiguration mInitialConfiguration; private NetworkAgent(@NonNull Looper looper, @NonNull Context context, @NonNull String logTag, - @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score, - @NonNull NetworkAgentConfig config, int providerId, @NonNull NetworkInfo ni) { + @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, + @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId, + @NonNull NetworkInfo ni) { mHandler = new NetworkAgentHandler(looper); LOG_TAG = logTag; mNetworkInfo = new NetworkInfo(ni); @@ -875,13 +888,22 @@ public abstract class NetworkAgent { /** * Must be called by the agent to update the score of this network. * + * @param score the new score. + * @hide TODO : unhide when impl is complete + */ + public final void sendNetworkScore(@NonNull NetworkScore score) { + Objects.requireNonNull(score); + queueOrSendMessage(reg -> reg.sendScore(score)); + } + + /** + * Must be called by the agent to update the score of this network. + * * @param score the new score, between 0 and 99. + * deprecated use sendNetworkScore(NetworkScore) TODO : remove in S. */ public final void sendNetworkScore(@IntRange(from = 0, to = 99) int score) { - if (score < 0) { - throw new IllegalArgumentException("Score must be >= 0"); - } - queueOrSendMessage(reg -> reg.sendScore(score)); + sendNetworkScore(new NetworkScore.Builder().setLegacyInt(score).build()); } /** diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java index 3fd95ee58df2..dbe3ecc4d775 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java +++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java @@ -140,7 +140,7 @@ public class NetworkRequest implements Parcelable { REQUEST, BACKGROUND_REQUEST, TRACK_SYSTEM_DEFAULT, - TRACK_BEST, + LISTEN_FOR_BEST, }; /** @@ -514,6 +514,15 @@ public class NetworkRequest implements Parcelable { } /** + * Returns true iff. this NetworkRequest is of type LISTEN_FOR_BEST. + * + * @hide + */ + public boolean isListenForBest() { + return type == Type.LISTEN_FOR_BEST; + } + + /** * Returns true iff. the contained NetworkRequest is one that: * * - should be associated with at most one satisfying network diff --git a/packages/Connectivity/framework/src/android/net/OemNetworkPreferences.java b/packages/Connectivity/framework/src/android/net/OemNetworkPreferences.java index 48bd29769f83..5a76cd6d6b0f 100644 --- a/packages/Connectivity/framework/src/android/net/OemNetworkPreferences.java +++ b/packages/Connectivity/framework/src/android/net/OemNetworkPreferences.java @@ -73,6 +73,14 @@ public final class OemNetworkPreferences implements Parcelable { private final Bundle mNetworkMappings; /** + * Return whether this object is empty. + * @hide + */ + public boolean isEmpty() { + return mNetworkMappings.keySet().size() == 0; + } + + /** * Return the currently built application package name to {@link OemNetworkPreference} mappings. * @return the current network preferences map. */ diff --git a/packages/Connectivity/framework/src/android/net/ParseException.java b/packages/Connectivity/framework/src/android/net/ParseException.java index bcfdd7ef09cc..ca6d012dfe7c 100644 --- a/packages/Connectivity/framework/src/android/net/ParseException.java +++ b/packages/Connectivity/framework/src/android/net/ParseException.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.NonNull; +import android.annotation.SystemApi; /** * Thrown when parsing failed. @@ -25,12 +26,16 @@ import android.annotation.NonNull; public class ParseException extends RuntimeException { public String response; - ParseException(@NonNull String response) { + /** @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public ParseException(@NonNull String response) { super(response); this.response = response; } - ParseException(@NonNull String response, @NonNull Throwable cause) { + /** @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public ParseException(@NonNull String response, @NonNull Throwable cause) { super(response, cause); this.response = response; } diff --git a/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java index c5100794899a..cd8f4c06de65 100644 --- a/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java +++ b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java @@ -22,9 +22,6 @@ import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; -import android.util.SparseArray; - -import com.android.internal.util.MessageUtils; import java.util.Objects; @@ -38,9 +35,6 @@ import java.util.Objects; */ @SystemApi(client = MODULE_LIBRARIES) public final class VpnTransportInfo implements TransportInfo, Parcelable { - private static final SparseArray<String> sTypeToString = - MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"}); - /** Type of this VPN. */ public final int type; @@ -63,8 +57,7 @@ public final class VpnTransportInfo implements TransportInfo, Parcelable { @Override public String toString() { - final String typeString = sTypeToString.get(type, "VPN_TYPE_???"); - return String.format("VpnTransportInfo{%s}", typeString); + return String.format("VpnTransportInfo{type=%d}", type); } @Override diff --git a/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java b/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java index 739ddada50b4..6a49aa2576c3 100644 --- a/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java +++ b/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java @@ -16,8 +16,8 @@ package android.net.util; -import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI; -import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE; +import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI; +import static android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE; import android.annotation.NonNull; import android.content.BroadcastReceiver; @@ -110,8 +110,8 @@ public class MultinetworkPolicyTracker { mHandler = handler; mAvoidBadWifiCallback = avoidBadWifiCallback; mSettingsUris = Arrays.asList( - Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI), - Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE)); + Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI), + Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE)); mResolver = mContext.getContentResolver(); mSettingObserver = new SettingObserver(); mBroadcastReceiver = new BroadcastReceiver() { diff --git a/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl index f0193db5c2e2..18d26a7e4baf 100644 --- a/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl +++ b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl @@ -19,11 +19,12 @@ import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; +import android.net.NetworkScore; import android.net.QosSession; import android.telephony.data.EpsBearerQosSessionAttributes; /** - * Interface for NetworkAgents to send network network properties. + * Interface for NetworkAgents to send network properties. * @hide */ oneway interface INetworkAgentRegistry { @@ -31,7 +32,7 @@ oneway interface INetworkAgentRegistry { void sendLinkProperties(in LinkProperties lp); // TODO: consider replacing this by "markConnected()" and removing void sendNetworkInfo(in NetworkInfo info); - void sendScore(int score); + void sendScore(in NetworkScore score); void sendExplicitlySelected(boolean explicitlySelected, boolean acceptPartial); void sendSocketKeepaliveEvent(int slot, int reason); void sendUnderlyingNetworks(in @nullable List<Network> networks); diff --git a/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml b/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml index 7d98c76a40ba..06c81921fd3f 100644 --- a/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml +++ b/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml @@ -42,4 +42,14 @@ --> </string-array> + <string-array translatable="false" name="config_legacy_networktype_restore_timers"> + <item>2,60000</item><!-- mobile_mms --> + <item>3,60000</item><!-- mobile_supl --> + <item>4,60000</item><!-- mobile_dun --> + <item>5,60000</item><!-- mobile_hipri --> + <item>10,60000</item><!-- mobile_fota --> + <item>11,60000</item><!-- mobile_ims --> + <item>12,60000</item><!-- mobile_cbs --> + </string-array> + </resources>
\ No newline at end of file diff --git a/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml b/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml index 00ec2df0e6f1..da8aee56276c 100644 --- a/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml +++ b/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml @@ -17,11 +17,11 @@ <overlayable name="ServiceConnectivityResourcesConfig"> <policy type="product|system|vendor"> <!-- Configuration values for ConnectivityService --> + <item type="array" name="config_legacy_networktype_restore_timers"/> <item type="string" name="config_networkCaptivePortalServerUrl"/> <item type="integer" name="config_networkTransitionTimeout"/> <item type="array" name="config_wakeonlan_supported_interfaces"/> - </policy> </overlayable> </resources> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index efa9f3c16b48..d801f1bbf5e5 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1067,8 +1067,8 @@ <![CDATA[ Adjust how colors display on your device. This can be helpful when you want to:<br/><br/> <ol> - <li> See colors more accurately</li> - <li> Remove colors to help you focus</li> + <li> See colors more accurately</li> + <li> Remove colors to help you focus</li> </ol> ]]></string> <!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] --> diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 9c0c80bff3df..bf4242f542da 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -23,7 +23,6 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.location.LocationManager; import android.media.AudioManager; -import android.net.ConnectivityManager; import android.net.NetworkCapabilities; import android.net.TetheringManager; import android.net.vcn.VcnTransportInfo; @@ -37,6 +36,7 @@ import android.provider.Settings; import android.telephony.AccessNetworkConstants; import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; +import android.telephony.TelephonyManager; import androidx.annotation.NonNull; import androidx.core.graphics.drawable.RoundedBitmapDrawable; @@ -439,8 +439,7 @@ public class Utils { } public static boolean isWifiOnly(Context context) { - return !context.getSystemService(ConnectivityManager.class) - .isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + return !context.getSystemService(TelephonyManager.class).isDataCapable(); } /** Returns if the automatic storage management feature is turned on or not. **/ diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java index 092cbf3c7c12..60bcf37304a5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java @@ -16,7 +16,6 @@ package com.android.settingslib.net; -import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.NetworkStatsHistory.FIELD_RX_BYTES; import static android.net.NetworkStatsHistory.FIELD_TX_BYTES; import static android.net.TrafficStats.MB_IN_BYTES; @@ -59,7 +58,6 @@ public class DataUsageController { PERIOD_BUILDER, Locale.getDefault()); private final Context mContext; - private final ConnectivityManager mConnectivityManager; private final INetworkStatsService mStatsService; private final NetworkPolicyManager mPolicyManager; private final NetworkStatsManager mNetworkStatsManager; @@ -71,7 +69,6 @@ public class DataUsageController { public DataUsageController(Context context) { mContext = context; - mConnectivityManager = ConnectivityManager.from(context); mStatsService = INetworkStatsService.Stub.asInterface( ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); mPolicyManager = NetworkPolicyManager.from(mContext); @@ -236,7 +233,7 @@ public class DataUsageController { public boolean isMobileDataSupported() { // require both supported network and ready SIM - return mConnectivityManager.isNetworkSupported(TYPE_MOBILE) + return getTelephonyManager().isDataCapable() && getTelephonyManager().getSimState() == SIM_STATE_READY; } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index 841a49e6d4fd..cbfd4d8ad07b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -75,7 +75,8 @@ public class WifiStatusTracker { .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build(); - private final NetworkCallback mNetworkCallback = new NetworkCallback() { + private final NetworkCallback mNetworkCallback = + new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) { @Override public void onAvailable( Network network, NetworkCapabilities networkCapabilities, @@ -131,7 +132,8 @@ public class WifiStatusTracker { } } }; - private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback() { + private final NetworkCallback mDefaultNetworkCallback = + new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) { @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { // network is now the default network, and its capabilities are nc. diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index 6568bffddecc..268603fa8b0d 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -2445,8 +2445,8 @@ class DatabaseHelper extends SQLiteOpenHelper { R.bool.def_auto_time_zone); // Sync timezone to NITZ loadSetting(stmt, Settings.Global.STAY_ON_WHILE_PLUGGED_IN, - ("1".equals(SystemProperties.get("ro.kernel.qemu")) || - res.getBoolean(R.bool.def_stay_on_while_plugged_in)) + ("1".equals(SystemProperties.get("ro.boot.qemu")) + || res.getBoolean(R.bool.def_stay_on_while_plugged_in)) ? 1 : 0); loadIntegerSetting(stmt, Settings.Global.WIFI_SLEEP_POLICY, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index cf66bad45f3b..db38ff64cb04 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -430,6 +430,9 @@ <!-- Permission required for CTS test - FontManagerTest --> <uses-permission android:name="android.permission.UPDATE_FONTS" /> + <!-- Permission required for hotword detection service CTS tests --> + <uses-permission android:name="android.permission.MANAGE_HOTWORD_DETECTION" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/SystemUI/res/color/remote_input_send.xml b/packages/SystemUI/res/color/remote_input_send.xml index fe2ffaa838eb..bd91ef9785d1 100644 --- a/packages/SystemUI/res/color/remote_input_send.xml +++ b/packages/SystemUI/res/color/remote_input_send.xml @@ -16,6 +16,6 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="true" android:color="@android:color/white" /> - <item android:color="#4dffffff" /> <!-- 30% white --> + <item android:state_enabled="true" android:color="?android:attr/colorAccent" /> + <item android:color="?android:attr/colorAccent" android:alpha=".3" /> </selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/color/remote_input_text.xml b/packages/SystemUI/res/color/remote_input_text.xml index 8e18e16f8abc..33eeb7794d09 100644 --- a/packages/SystemUI/res/color/remote_input_text.xml +++ b/packages/SystemUI/res/color/remote_input_text.xml @@ -16,6 +16,6 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="true" android:color="@color/remote_input_text_enabled" /> <!-- white --> - <item android:color="#99ffffff" /> <!-- 60% white --> + <item android:state_enabled="true" android:color="?android:attr/textColorTertiary" /> + <item android:color="?android:attr/textColorTertiary" android:alpha=".6" /> </selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_qs_wallet.xml b/packages/SystemUI/res/drawable/ic_qs_wallet.xml new file mode 100644 index 000000000000..e146eabecfd3 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_wallet.xml @@ -0,0 +1,11 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M20,4L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2, + -0.89 2,-2L22,6c0,-1.11 -0.89,-2 -2,-2zM20,18L4,18v-6h16v6zM20,8L4,8L4,6h16v2z"/> +</vector> diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml index b5d48b4636a8..43182eb991e5 100644 --- a/packages/SystemUI/res/layout/remote_input.xml +++ b/packages/SystemUI/res/layout/remote_input.xml @@ -19,7 +19,6 @@ <!-- LinearLayout --> <com.android.systemui.statusbar.policy.RemoteInputView xmlns:android="http://schemas.android.com/apk/res/android" - android:theme="@style/systemui_theme_remote_input" android:id="@+id/remote_input" android:layout_height="match_parent" android:layout_width="match_parent"> @@ -33,6 +32,10 @@ android:paddingBottom="4dp" android:paddingStart="16dp" android:paddingEnd="12dp" + android:layout_marginRight="5dp" + android:layout_marginLeft="20dp" + android:layout_marginTop="5dp" + android:layout_marginBottom="20dp" android:gravity="start|center_vertical" android:textAppearance="?android:attr/textAppearance" android:textColor="@color/remote_input_text" @@ -53,6 +56,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center" + android:paddingBottom="20dp" android:paddingStart="12dp" android:paddingEnd="24dp" android:id="@+id/remote_input_send" @@ -66,6 +70,7 @@ android:id="@+id/remote_input_progress" android:layout_width="24dp" android:layout_height="24dp" + android:layout_marginBottom="10dp" android:layout_marginEnd="6dp" android:layout_gravity="center" android:visibility="invisible" diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 424172458b80..d4783197e1f1 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -153,10 +153,9 @@ <color name="minimize_dock_shadow_end">#00000000</color> <color name="default_remote_input_background">@*android:color/notification_default_color</color> - <color name="remote_input_text_enabled">#ffffffff</color> <color name="remote_input_hint">#99ffffff</color> - <color name="remote_input_accent">#eeeeee</color> + <color name="remote_input_accent">?android:attr/colorAccent</color> <color name="quick_step_track_background_background_dark">#1F000000</color> <color name="quick_step_track_background_background_light">#33FFFFFF</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index b0c5239bb4b8..af6df32a02b0 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -107,7 +107,7 @@ <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" --> <string name="quick_settings_tiles_stock" translatable="false"> - wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls,alarm + wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls,alarm,wallet </string> <!-- The tiles to display in QuickSettings --> diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index 8cd5757247e2..2163806666d9 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -38,6 +38,8 @@ <!-- People Tile flag --> <bool name="flag_conversations">false</bool> + <bool name="flag_wallet">false</bool> + <!-- The new animations to/from lockscreen and AOD! --> <bool name="flag_lockscreen_animations">false</bool> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 57c932eaa8e4..78180a7a80a8 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1337,7 +1337,7 @@ <string name="monitoring_description_named_management">This device belongs to <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g>.\n\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nFor more information, contact your IT admin.</string> <!-- Dialog that a user can access via Quick Settings. The dialog describes what a Creditor can monitor (and the changes they can make) on the user's financed device. [CHAR LIMIT=NONE]--> - <string name="monitoring_financed_description_named_management"><xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> may be able to access data associated with this device and change this device\’s settings.\n\nIf you have questions, contact <xliff:g id="organization_name" example="Foo, Inc.">%2$s</xliff:g>.</string> + <string name="monitoring_financed_description_named_management"><xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> may be able to access data associated with this device, manage apps, and change this device\’s settings.\n\nIf you have questions, contact <xliff:g id="organization_name" example="Foo, Inc.">%2$s</xliff:g>.</string> <!-- Dialog that a user can access via Quick Settings. The dialog describes what the IT admin can monitor (and the changes they can make) on the user's device. [CHAR LIMIT=NONE]--> <string name="monitoring_description_management">This device belongs to your organization.\n\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nFor more information, contact your IT admin.</string> @@ -1621,6 +1621,12 @@ <!-- Name of the alarm status bar icon. --> <string name="status_bar_alarm">Alarm</string> + <!-- Wallet strings --> + <!-- Wallet empty state, title [CHAR LIMIT=32] --> + <string name="wallet_title">Wallet</string> + <!-- Secondary label of the quick access wallet tile. [CHAR LIMIT=32] --> + <string name="wallet_secondary_label">Ready</string> + <!-- Name of the work status bar icon. --> <string name="status_bar_work">Work profile</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index fb885cb3fdbe..ff9ea0175ec0 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -384,10 +384,6 @@ <!-- Overridden by values-television/styles.xml with tv-specific settings --> <style name="volume_dialog_theme" parent="qs_theme"/> - <style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault.Light"> - <item name="android:colorAccent">@color/remote_input_accent</item> - </style> - <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog" /> <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" /> diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index a9b4c492e10c..7966b388bc4f 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -25,9 +25,12 @@ import android.os.HandlerThread; import android.os.SystemClock; import android.os.Trace; import android.service.wallpaper.WallpaperService; +import android.util.ArraySet; import android.util.Log; +import android.util.MathUtils; import android.util.Size; import android.view.SurfaceHolder; +import android.view.WindowManager; import androidx.annotation.NonNull; @@ -54,7 +57,10 @@ public class ImageWallpaper extends WallpaperService { private static final @android.annotation.NonNull RectF LOCAL_COLOR_BOUNDS = new RectF(0, 0, 1, 1); private static final boolean DEBUG = false; - private ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>(); + private final ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>(); + private final ArraySet<RectF> mColorAreas = new ArraySet<>(); + private float mShift; + private volatile int mPages; private HandlerThread mWorker; // scaled down version private Bitmap mMiniBitmap; @@ -96,6 +102,10 @@ public class ImageWallpaper extends WallpaperService { private EglHelper mEglHelper; private final Runnable mFinishRenderingTask = this::finishRendering; private boolean mNeedRedraw; + private int mWidth = 1; + private int mHeight = 1; + private int mImgWidth = 1; + private int mImgHeight = 1; GLEngine() { } @@ -111,8 +121,13 @@ public class ImageWallpaper extends WallpaperService { // Deferred init renderer because we need to get wallpaper by display context. mRenderer = getRendererInstance(); setFixedSizeAllowed(true); - setOffsetNotificationsEnabled(false); updateSurfaceSize(); + Rect window = getDisplayContext() + .getSystemService(WindowManager.class) + .getCurrentWindowMetrics() + .getBounds(); + mHeight = window.height(); + mWidth = window.width(); mMiniBitmap = null; if (mWorker != null && mWorker.getThreadHandler() != null) { mWorker.getThreadHandler().post(this::updateMiniBitmap); @@ -127,6 +142,41 @@ public class ImageWallpaper extends WallpaperService { return new ImageWallpaperRenderer(getDisplayContext()); } + @Override + public void onOffsetsChanged(float xOffset, float yOffset, + float xOffsetStep, float yOffsetStep, + int xPixelOffset, int yPixelOffset) { + if (mMiniBitmap == null || mMiniBitmap.isRecycled()) return; + final int pages; + if (xOffsetStep > 0 && xOffsetStep <= 1) { + pages = (int) (1 / xOffsetStep + 1); + } else { + pages = 1; + } + if (pages == mPages) return; + mPages = pages; + updateShift(); + mWorker.getThreadHandler().post(() -> + computeAndNotifyLocalColors(new ArrayList<>(mColorAreas), mMiniBitmap)); + } + + private void updateShift() { + if (mImgHeight == 0) { + mShift = 0; + return; + } + // calculate shift + float imgWidth = (float) mImgWidth / (float) mImgHeight; + float displayWidth = + (float) mWidth / (float) mHeight; + // if need to shift + if (imgWidth > displayWidth) { + mShift = imgWidth / imgWidth - displayWidth / imgWidth; + } else { + mShift = 0; + } + } + private void updateMiniBitmap() { mRenderer.useBitmap(b -> { int size = Math.min(b.getWidth(), b.getHeight()); @@ -134,6 +184,8 @@ public class ImageWallpaper extends WallpaperService { if (size > MIN_SURFACE_WIDTH) { scale = (float) MIN_SURFACE_WIDTH / (float) size; } + mImgHeight = b.getHeight(); + mImgWidth = b.getWidth(); mMiniBitmap = Bitmap.createScaledBitmap(b, Math.round(scale * b.getWidth()), Math.round(scale * b.getHeight()), false); computeAndNotifyLocalColors(mLocalColorsToAdd, mMiniBitmap); @@ -173,6 +225,9 @@ public class ImageWallpaper extends WallpaperService { @Override public void addLocalColorsAreas(@NonNull List<RectF> regions) { mWorker.getThreadHandler().post(() -> { + if (mColorAreas.size() + mLocalColorsToAdd.size() == 0) { + setOffsetNotificationsEnabled(true); + } Bitmap bitmap = mMiniBitmap; if (bitmap == null) { mLocalColorsToAdd.addAll(regions); @@ -184,6 +239,7 @@ public class ImageWallpaper extends WallpaperService { private void computeAndNotifyLocalColors(@NonNull List<RectF> regions, Bitmap b) { List<WallpaperColors> colors = getLocalWallpaperColors(regions, b); + mColorAreas.addAll(regions); try { notifyLocalColorsChanged(regions, colors); } catch (RuntimeException e) { @@ -193,14 +249,45 @@ public class ImageWallpaper extends WallpaperService { @Override public void removeLocalColorsAreas(@NonNull List<RectF> regions) { - // No-OP + mWorker.getThreadHandler().post(() -> { + mColorAreas.removeAll(regions); + mLocalColorsToAdd.removeAll(regions); + if (mColorAreas.size() + mLocalColorsToAdd.size() == 0) { + setOffsetNotificationsEnabled(false); + } + }); + } + + private RectF pageToImgRect(RectF area) { + float pageWidth = 1f / (float) mPages; + if (pageWidth < 1 && pageWidth >= 0) pageWidth = 1; + float imgWidth = (float) mImgWidth / (float) mImgHeight; + float displayWidth = + (float) mWidth / (float) mHeight; + float expansion = imgWidth > displayWidth ? displayWidth / imgWidth : 1; + int page = (int) Math.floor(area.centerX() / pageWidth); + float shiftWidth = mShift * page * pageWidth; + RectF imgArea = new RectF(); + imgArea.bottom = area.bottom; + imgArea.top = area.top; + imgArea.left = MathUtils.constrain(area.left % pageWidth, 0, 1) + * expansion + shiftWidth; + imgArea.right = MathUtils.constrain(area.right % pageWidth, 0, 1) + * expansion + shiftWidth; + if (imgArea.left > imgArea.right) { + // take full page + imgArea.left = shiftWidth; + imgArea.right = 1 - (mShift - shiftWidth); + } + return imgArea; } private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas, Bitmap b) { List<WallpaperColors> colors = new ArrayList<>(areas.size()); + updateShift(); for (int i = 0; i < areas.size(); i++) { - RectF area = areas.get(i); + RectF area = pageToImgRect(areas.get(i)); if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) { colors.add(null); continue; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java index c1cf8d31bd67..9f77b7d26c96 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java @@ -19,6 +19,7 @@ package com.android.systemui.accessibility; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; import android.annotation.NonNull; +import android.annotation.UiContext; import android.content.Context; import android.content.pm.ActivityInfo; import android.graphics.PixelFormat; @@ -69,7 +70,7 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL private final MagnificationGestureDetector mGestureDetector; private boolean mSingleTapDetected = false; - MagnificationModeSwitch(Context context) { + MagnificationModeSwitch(@UiContext Context context) { this(context, createView(context)); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java index 50f6e9f6af44..1a01ad85fccc 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility; +import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; + import android.annotation.MainThread; import android.content.Context; import android.hardware.display.DisplayManager; @@ -106,10 +108,9 @@ public class ModeSwitchesController { @Override protected MagnificationModeSwitch createInstance(Display display) { - final Context context = (display.getDisplayId() == Display.DEFAULT_DISPLAY) - ? mContext - : mContext.createDisplayContext(display); - return new MagnificationModeSwitch(context); + final Context uiContext = mContext.createWindowContext(display, + TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null); + return new MagnificationModeSwitch(uiContext); } } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index f52dcd596a55..cdd69429132a 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility; +import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; + import android.annotation.MainThread; import android.annotation.Nullable; import android.content.Context; @@ -82,9 +84,8 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall @Override protected WindowMagnificationAnimationController createInstance(Display display) { - final Context context = (display.getDisplayId() == Display.DEFAULT_DISPLAY) - ? mContext - : mContext.createDisplayContext(display); + final Context windowContext = mContext.createWindowContext(display, + TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null); final WindowMagnificationController controller = new WindowMagnificationController( mContext, mHandler, new SfVsyncFrameCallbackProvider(), null, @@ -92,7 +93,7 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall final int navBarMode = mNavigationModeController.addListener( controller::onNavigationModeChanged); controller.onNavigationModeChanged(navBarMode); - return new WindowMagnificationAnimationController(context, controller); + return new WindowMagnificationAnimationController(windowContext, controller); } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java index 24d83884f093..5758b1575f5a 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java @@ -20,6 +20,7 @@ import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.IntDef; import android.annotation.Nullable; +import android.annotation.UiContext; import android.content.Context; import android.content.res.Resources; import android.os.RemoteException; @@ -69,8 +70,8 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp @MagnificationState private int mState = STATE_DISABLED; - WindowMagnificationAnimationController( - Context context, WindowMagnificationController controller) { + WindowMagnificationAnimationController(@UiContext Context context, + WindowMagnificationController controller) { this(context, controller, newValueAnimator(context.getResources())); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 3f7d2d837e2d..2b666f13efcc 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -24,6 +24,7 @@ import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UiContext; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Resources; @@ -134,7 +135,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold @Nullable private MirrorWindowControl mMirrorWindowControl; - WindowMagnificationController(Context context, @NonNull Handler handler, + WindowMagnificationController(@UiContext Context context, @NonNull Handler handler, SfVsyncFrameCallbackProvider sfVsyncFrameProvider, MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction, @NonNull WindowMagnifierCallback callback) { @@ -147,7 +148,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mDisplayId = mContext.getDisplayId(); mRotation = display.getRotation(); - mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mWm = context.getSystemService(WindowManager.class); mResources = mContext.getResources(); mScale = mResources.getInteger(R.integer.magnification_default_scale); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java index a78c223bf883..378907c400d7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java @@ -73,6 +73,16 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { } @Override + void onIlluminationStarting() { + setVisibility(View.INVISIBLE); + } + + @Override + void onIlluminationStopped() { + setVisibility(View.VISIBLE); + } + + @Override public boolean dozeTimeTick() { // TODO: burnin mFingerprintDrawable.dozeTimeTick(); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index 123ccee80179..8e344d2c2df7 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -31,6 +31,7 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.ServiceManager; import android.os.UserHandle; +import android.service.quickaccesswallet.QuickAccessWalletClient; import android.view.Choreographer; import android.view.IWindowManager; import android.view.LayoutInflater; @@ -360,4 +361,11 @@ public class DependencyProvider { public ModeSwitchesController providesModeSwitchesController(Context context) { return new ModeSwitchesController(context); } + + /** */ + @Provides + @SysUISingleton + public QuickAccessWalletClient provideQuickAccessWalletClient(Context context) { + return QuickAccessWalletClient.create(context); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 2cd367d03102..3e3451e64b49 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -445,11 +445,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D final ArrayList<String> tiles = new ArrayList<String>(); boolean addedDefault = false; Set<String> addedSpecs = new ArraySet<>(); - // TODO(b/174753536): Move it into the config file. - if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { - tiles.add("internet"); - addedSpecs.add("internet"); - } for (String tile : tileList.split(",")) { tile = tile.trim(); if (tile.isEmpty()) continue; @@ -457,17 +452,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D if (!addedDefault) { List<String> defaultSpecs = getDefaultSpecs(context); for (String spec : defaultSpecs) { - // TODO(b/174753536): Move it into the config file. - if (FeatureFlagUtils.isEnabled( - context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { - if (spec.equals("wifi") || spec.equals("cell")) { - continue; - } - } else { - if (spec.equals("internet")) { - continue; - } - } if (!addedSpecs.contains(spec)) { tiles.add(spec); addedSpecs.add(spec); @@ -476,18 +460,40 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D addedDefault = true; } } else { - // TODO(b/174753536): Move it into the config file. - if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { - if (tile.equals("wifi") || tile.equals("cell")) { - continue; - } - } if (!addedSpecs.contains(tile)) { tiles.add(tile); addedSpecs.add(tile); } } } + // TODO(b/174753536): Move it into the config file. + // Only do the below hacking when at least one of the below tiles exist + // --InternetTile + // --WiFiTile + // --CellularTIle + if (tiles.contains("internet") || tiles.contains("wifi") || tiles.contains("cell")) { + if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { + if (!tiles.contains("internet")) { + tiles.add("internet"); + } + if (tiles.contains("wifi")) { + tiles.remove("wifi"); + } + if (tiles.contains("cell")) { + tiles.remove("cell"); + } + } else { + if (tiles.contains("internet")) { + tiles.remove("internet"); + } + if (!tiles.contains("wifi")) { + tiles.add("wifi"); + } + if (!tiles.contains("cell")) { + tiles.add("cell"); + } + } + } return tiles; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index 7eeb4bd19f1a..32b41ec0ab66 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -115,9 +115,32 @@ public class TileQueryHelper { final ArrayList<QSTile> tilesToAdd = new ArrayList<>(); // TODO(b/174753536): Move it into the config file. - if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { - if (!possibleTiles.contains("internet")) { - possibleTiles.add("internet"); + // Only do the below hacking when at least one of the below tiles exist + // --InternetTile + // --WiFiTile + // --CellularTIle + if (possibleTiles.contains("internet") || possibleTiles.contains("wifi") + || possibleTiles.contains("cell")) { + if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { + if (!possibleTiles.contains("internet")) { + possibleTiles.add("internet"); + } + if (possibleTiles.contains("wifi")) { + possibleTiles.remove("wifi"); + } + if (possibleTiles.contains("cell")) { + possibleTiles.remove("cell"); + } + } else { + if (possibleTiles.contains("internet")) { + possibleTiles.remove("internet"); + } + if (!possibleTiles.contains("wifi")) { + possibleTiles.add("wifi"); + } + if (!possibleTiles.contains("cell")) { + possibleTiles.add("cell"); + } } } for (String spec : possibleTiles) { @@ -125,15 +148,6 @@ public class TileQueryHelper { // Do not include CustomTile. Those will be created by `addPackageTiles`. if (spec.startsWith(CustomTile.PREFIX)) continue; // TODO(b/174753536): Move it into the config file. - if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { - if (spec.equals("wifi") || spec.equals("cell")) { - continue; - } - } else { - if (spec.equals("internet")) { - continue; - } - } final QSTile tile = host.createTile(spec); if (tile == null) { continue; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 29b9e64d1659..ba349c6273d9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -47,6 +47,7 @@ import com.android.systemui.qs.tiles.LocationTile; import com.android.systemui.qs.tiles.MicrophoneToggleTile; import com.android.systemui.qs.tiles.NfcTile; import com.android.systemui.qs.tiles.NightDisplayTile; +import com.android.systemui.qs.tiles.QuickAccessWalletTile; import com.android.systemui.qs.tiles.ReduceBrightColorsTile; import com.android.systemui.qs.tiles.RotationLockTile; import com.android.systemui.qs.tiles.ScreenRecordTile; @@ -93,6 +94,7 @@ public class QSFactoryImpl implements QSFactory { private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider; private final Provider<DeviceControlsTile> mDeviceControlsTileProvider; private final Provider<AlarmTile> mAlarmTileProvider; + private final Provider<QuickAccessWalletTile> mQuickAccessWalletTileProvider; private final Lazy<QSHost> mQsHostLazy; private final Provider<CustomTile.Builder> mCustomTileBuilderProvider; @@ -129,7 +131,8 @@ public class QSFactoryImpl implements QSFactory { Provider<CameraToggleTile> cameraToggleTileProvider, Provider<MicrophoneToggleTile> microphoneToggleTileProvider, Provider<DeviceControlsTile> deviceControlsTileProvider, - Provider<AlarmTile> alarmTileProvider) { + Provider<AlarmTile> alarmTileProvider, + Provider<QuickAccessWalletTile> quickAccessWalletTileProvider) { mQsHostLazy = qsHostLazy; mCustomTileBuilderProvider = customTileBuilderProvider; @@ -161,6 +164,7 @@ public class QSFactoryImpl implements QSFactory { mMicrophoneToggleTileProvider = microphoneToggleTileProvider; mDeviceControlsTileProvider = deviceControlsTileProvider; mAlarmTileProvider = alarmTileProvider; + mQuickAccessWalletTileProvider = quickAccessWalletTileProvider; } public QSTile createTile(String tileSpec) { @@ -224,6 +228,8 @@ public class QSFactoryImpl implements QSFactory { return mDeviceControlsTileProvider.get(); case "alarm": return mAlarmTileProvider.get(); + case "wallet": + return mQuickAccessWalletTileProvider.get(); } // Custom tiles diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java new file mode 100644 index 000000000000..60c5d1cafde9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT; + +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Handler; +import android.os.Looper; +import android.service.quickaccesswallet.QuickAccessWalletClient; +import android.service.quicksettings.Tile; + +import com.android.internal.logging.MetricsLogger; +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.qs.QSTile; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.statusbar.FeatureFlags; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.settings.SecureSettings; + +import javax.inject.Inject; + +/** Quick settings tile: Quick access wallet **/ +public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { + + private static final String FEATURE_CHROME_OS = "org.chromium.arc"; + private final CharSequence mLabel = mContext.getString(R.string.wallet_title); + // TODO(b/180959290): Re-create the QAW Client when the default NFC payment app changes. + private final QuickAccessWalletClient mQuickAccessWalletClient; + private final KeyguardStateController mKeyguardStateController; + private final PackageManager mPackageManager; + private final SecureSettings mSecureSettings; + private final FeatureFlags mFeatureFlags; + + @Inject + public QuickAccessWalletTile( + QSHost host, + @Background Looper backgroundLooper, + @Main Handler mainHandler, + FalsingManager falsingManager, + MetricsLogger metricsLogger, + StatusBarStateController statusBarStateController, + ActivityStarter activityStarter, + QSLogger qsLogger, + QuickAccessWalletClient quickAccessWalletClient, + KeyguardStateController keyguardStateController, + PackageManager packageManager, + SecureSettings secureSettings, + FeatureFlags featureFlags) { + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); + mQuickAccessWalletClient = quickAccessWalletClient; + mKeyguardStateController = keyguardStateController; + mPackageManager = packageManager; + mSecureSettings = secureSettings; + mFeatureFlags = featureFlags; + } + + + @Override + public State newTileState() { + State state = new State(); + state.handlesLongClick = false; + return state; + } + + @Override + protected void handleClick() { + mActivityStarter.postStartActivityDismissingKeyguard( + mQuickAccessWalletClient.createWalletIntent(), /* delay= */ 0); + } + + @Override + protected void handleUpdateState(State state, Object arg) { + CharSequence qawLabel = mQuickAccessWalletClient.getServiceLabel(); + state.label = qawLabel == null ? mLabel : qawLabel; + state.contentDescription = state.label; + state.icon = ResourceIcon.get(R.drawable.ic_qs_wallet); + boolean isDeviceLocked = !mKeyguardStateController.isUnlocked(); + if (mQuickAccessWalletClient.isWalletFeatureAvailable()) { + state.state = isDeviceLocked ? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE; + state.secondaryLabel = isDeviceLocked + ? null + : mContext.getString(R.string.wallet_secondary_label); + state.stateDescription = state.secondaryLabel; + } else { + state.state = Tile.STATE_UNAVAILABLE; + } + } + + @Override + public int getMetricsCategory() { + return 0; + } + + @Override + public boolean isAvailable() { + return mFeatureFlags.isQuickAccessWalletEnabled() + && mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION) + && !mPackageManager.hasSystemFeature(FEATURE_CHROME_OS) + && mSecureSettings.getString(NFC_PAYMENT_DEFAULT_COMPONENT) != null; + } + + @Override + public Intent getLongClickIntent() { + return null; + } + + @Override + public CharSequence getTileLabel() { + CharSequence qawLabel = mQuickAccessWalletClient.getServiceLabel(); + return qawLabel == null ? mLabel : qawLabel; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java index c8e0d60ce304..c3de81c3c66a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java @@ -83,6 +83,10 @@ public class FeatureFlags { return mFlagReader.isEnabled(R.bool.flag_monet); } + public boolean isQuickAccessWalletEnabled() { + return mFlagReader.isEnabled(R.bool.flag_wallet); + } + public boolean isNavigationBarOverlayEnabled() { return mFlagReader.isEnabled(R.bool.flag_navigation_bar_overlay); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 6ba52156c374..5219ecd1d83c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -16,16 +16,23 @@ package com.android.systemui.statusbar; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.text.format.DateFormat; import android.util.FloatProperty; import android.util.Log; +import android.view.View; import android.view.animation.Interpolator; import androidx.annotation.NonNull; import com.android.internal.annotations.GuardedBy; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; @@ -81,6 +88,8 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll // Record the HISTORY_SIZE most recent states private int mHistoryIndex = 0; private HistoricalState[] mHistoricalRecords = new HistoricalState[HISTORY_SIZE]; + // This is used by InteractionJankMonitor to get callback from HWUI. + private View mView; /** * If any of the system bars is hidden. @@ -236,6 +245,11 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll @Override public void setDozeAmount(float dozeAmount, boolean animated) { + setAndInstrumentDozeAmount(null, dozeAmount, animated); + } + + @Override + public void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated) { if (mDarkAnimator != null && mDarkAnimator.isRunning()) { if (animated && mDozeAmountTarget == dozeAmount) { return; @@ -244,6 +258,11 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll } } + // We don't need a new attached view if we already have one. + if ((mView == null || !mView.isAttachedToWindow()) + && (view != null && view.isAttachedToWindow())) { + mView = view; + } mDozeAmountTarget = dozeAmount; if (animated) { startDozeAnimation(); @@ -261,6 +280,22 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll mDarkAnimator = ObjectAnimator.ofFloat(this, SET_DARK_AMOUNT_PROPERTY, mDozeAmountTarget); mDarkAnimator.setInterpolator(Interpolators.LINEAR); mDarkAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP); + mDarkAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + cancelInteractionJankMonitor(); + } + + @Override + public void onAnimationEnd(Animator animation) { + endInteractionJankMonitor(); + } + + @Override + public void onAnimationStart(Animator animation) { + beginInteractionJankMonitor(); + } + }); mDarkAnimator.start(); } @@ -277,6 +312,24 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll } } + private void beginInteractionJankMonitor() { + if (mView != null && mView.isAttachedToWindow()) { + InteractionJankMonitor.getInstance().begin(mView, getCujType()); + } + } + + private void endInteractionJankMonitor() { + InteractionJankMonitor.getInstance().end(getCujType()); + } + + private void cancelInteractionJankMonitor() { + InteractionJankMonitor.getInstance().cancel(getCujType()); + } + + private int getCujType() { + return mIsDozing ? CUJ_LOCKSCREEN_TRANSITION_TO_AOD : CUJ_LOCKSCREEN_TRANSITION_FROM_AOD; + } + @Override public boolean goingToFullShade() { return mState == StatusBarState.SHADE && mLeaveOpenOnKeyguardHide; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java index a2e07b289e9d..b6d6ed53b681 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; +import android.view.View; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.StatusBar; @@ -75,6 +76,15 @@ public interface SysuiStatusBarStateController extends StatusBarStateController */ void setDozeAmount(float dozeAmount, boolean animated); + /** + * Changes the current doze amount, also starts the + * {@link com.android.internal.jank.InteractionJankMonitor InteractionJankMonitor} as possible. + * + * @param view An attached view, which will be used by InteractionJankMonitor. + * @param dozeAmount New doze/dark amount. + * @param animated If change should be animated or not. This will cancel current animations. + */ + void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated); /** * Update the expanded state from {@link StatusBar}'s perspective diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 8c21e767c5c9..55a27b2b0052 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -22,8 +22,6 @@ import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; @@ -32,7 +30,6 @@ import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; -import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.NotificationHeaderView; @@ -44,7 +41,6 @@ import android.widget.ImageView; import android.widget.LinearLayout; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ContrastColorUtil; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; @@ -1274,23 +1270,6 @@ public class NotificationContentView extends FrameLayout { } } if (hasRemoteInput) { - int color = entry.getSbn().getNotification().color; - if (color == Notification.COLOR_DEFAULT) { - color = mContext.getColor(R.color.default_remote_input_background); - } - if (mContext.getResources().getBoolean( - com.android.internal.R.bool.config_tintNotificationsWithTheme)) { - Resources.Theme theme = new ContextThemeWrapper(mContext, - com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme(); - TypedArray ta = theme.obtainStyledAttributes( - new int[]{com.android.internal.R.attr.colorAccent}); - color = ta.getColor(0, color); - ta.recycle(); - } - existing.setBackgroundColor(ContrastColorUtil.ensureTextBackgroundColor(color, - mContext.getColor(R.color.remote_input_text_enabled), - mContext.getColor(R.color.remote_input_hint))); - existing.setWrapper(wrapper); existing.setOnVisibilityChangedListener(this::setRemoteInputVisible); @@ -1312,6 +1291,10 @@ public class NotificationContentView extends FrameLayout { } } } + + if (existing != null && entry.getSbn().getNotification().isColorized()) { + existing.overrideBackgroundTintColor(entry.getSbn().getNotification().color); + } return existing; } return null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index acb3e5783525..cd712044a60e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -3127,7 +3127,7 @@ public class NotificationPanelViewController extends PanelViewController { } final float dozeAmount = dozing ? 1 : 0; - mStatusBarStateController.setDozeAmount(dozeAmount, animate); + mStatusBarStateController.setAndInstrumentDozeAmount(mView, dozeAmount, animate); } public void setPulsing(boolean pulsing) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index fbdaf9cdae20..db039b44d91a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -31,6 +31,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkScoreManager; @@ -307,7 +308,8 @@ public class NetworkControllerImpl extends BroadcastReceiver mWifiManager.registerScanResultsCallback(mReceiverHandler::post, scanResultsCallback); } - ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback(){ + NetworkCallback callback = + new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO){ private Network mLastNetwork; private NetworkCapabilities mLastNetworkCapabilities; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 320b00af0fc5..f72d2ae191d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -31,8 +31,15 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ShortcutManager; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Color; import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; import android.net.Uri; import android.os.Bundle; import android.os.ServiceManager; @@ -70,6 +77,7 @@ import androidx.annotation.NonNull; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.util.ContrastColorUtil; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -137,12 +145,40 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene ServiceManager.getService(Context.STATUS_BAR_SERVICE)); } + /** + * The remote view needs to adapt to colorized notifications when set + * @param color colorized notification color + */ + public void overrideBackgroundTintColor(int color) { + mEditText.setBackgroundTintColor(color); + final boolean dark = !ContrastColorUtil.isColorLight(color); + int[][] states = new int[][] { + new int[] {android.R.attr.state_enabled}, + new int[] {}, + }; + + final int finalColor = dark + ? Color.WHITE + : Color.BLACK; + + int[] colors = new int[] { + finalColor, + finalColor & 0x4DFFFFFF // %30 opacity + }; + + final ColorStateList tint = new ColorStateList(states, colors); + mSendButton.setImageTintList(tint); + mProgressBar.setProgressTintList(tint); + mProgressBar.setIndeterminateTintList(tint); + mProgressBar.setSecondaryProgressTintList(tint); + mEditText.setForegroundColor(finalColor); + } + @Override protected void onFinishInflate() { super.onFinishInflate(); mProgressBar = findViewById(R.id.remote_input_progress); - mSendButton = findViewById(R.id.remote_input_send); mSendButton.setOnClickListener(this); @@ -362,6 +398,12 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } } + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + mEditText.updateCornerRadius(heightMeasureSpec / 2); + } + /** Populates the text field of the remote input with the given content. */ public void setEditTextContent(@Nullable CharSequence editTextContent) { mEditText.setText(editTextContent); @@ -651,17 +693,47 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private final OnReceiveContentListener mOnReceiveContentListener = this::onReceiveContent; - private final Drawable mBackground; private RemoteInputView mRemoteInputView; + private GradientDrawable mTextBackground; + private ColorDrawable mBackgroundColor; + private LayerDrawable mBackground; boolean mShowImeOnInputConnection; private LightBarController mLightBarController; private InputMethodManager mInputMethodManager; + private int mColor = Notification.COLOR_DEFAULT; UserHandle mUser; + private int mStokeWidth; public RemoteEditText(Context context, AttributeSet attrs) { super(context, attrs); - mBackground = getBackground(); mLightBarController = Dependency.get(LightBarController.class); + mTextBackground = createBackground(context, attrs); + mBackgroundColor = new ColorDrawable(); + mBackground = new LayerDrawable(new Drawable[] {mBackgroundColor, mTextBackground}); + float density = context.getResources().getDisplayMetrics().density; + mStokeWidth = (int) (2 * density); + setDefaultColors(); + } + + private void setDefaultColors() { + Resources.Theme theme = getContext().getTheme(); + TypedArray ta = theme.obtainStyledAttributes( + new int[]{android.R.attr.colorAccent, + com.android.internal.R.attr.colorBackgroundFloating}); + mTextBackground.setStroke(mStokeWidth, + ta.getColor(0, Notification.COLOR_DEFAULT)); + mColor = ta.getColor(1, Notification.COLOR_DEFAULT); + mTextBackground.setColor(mColor); + } + + private GradientDrawable createBackground(Context context, AttributeSet attrs) { + float density = context.getResources().getDisplayMetrics().density; + int padding = (int) (12 * density); + GradientDrawable d = new GradientDrawable(); + d.setShape(GradientDrawable.RECTANGLE); + d.setPadding(padding, padding, padding, padding); + d.setCornerRadius(padding); + return d; } void setSupportedMimeTypes(@Nullable Collection<String> mimeTypes) { @@ -724,6 +796,19 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } } + protected void setBackgroundTintColor(int color) { + mBackgroundColor.setColor(color); + mTextBackground.setColor(color); + } + + protected void setForegroundColor(int color) { + mTextBackground.setStroke(mStokeWidth, color); + setTextColor(color); + // %60 + setHintTextColor(color & 0x99FFFFFF); + setTextCursorDrawable(null); + } + @Override public void getFocusedRect(Rect r) { super.getFocusedRect(r); @@ -811,6 +896,10 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene setSelection(getText().length()); } + void updateCornerRadius(float radius) { + mTextBackground.setCornerRadius(radius); + } + void setInnerFocusable(boolean focusable) { setFocusableInTouchMode(focusable); setFocusable(focusable); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java new file mode 100644 index 000000000000..33166ccadc27 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import static android.content.pm.PackageManager.FEATURE_NFC_HOST_CARD_EMULATION; +import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertTrue; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Handler; +import android.service.quickaccesswallet.QuickAccessWalletClient; +import android.service.quicksettings.Tile; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.testing.UiEventLoggerFake; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.qs.QSTile; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.qs.QSTileHost; +import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.statusbar.FeatureFlags; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.settings.SecureSettings; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +public class QuickAccessWalletTileTest extends SysuiTestCase { + + @Mock + private QSTileHost mHost; + @Mock + private MetricsLogger mMetricsLogger; + @Mock + private StatusBarStateController mStatusBarStateController; + @Mock + private ActivityStarter mActivityStarter; + @Mock + private QSLogger mQSLogger; + private UiEventLogger mUiEventLogger = new UiEventLoggerFake(); + @Mock + private QuickAccessWalletClient mQuickAccessWalletClient; + @Mock + private KeyguardStateController mKeyguardStateController; + @Mock + private PackageManager mPackageManager; + @Mock + private SecureSettings mSecureSettings; + @Mock + private FeatureFlags mFeatureFlags; + + private TestableLooper mTestableLooper; + private QuickAccessWalletTile mTile; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mTestableLooper = TestableLooper.get(this); + + when(mHost.getContext()).thenReturn(mContext); + when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger); + when(mFeatureFlags.isQuickAccessWalletEnabled()).thenReturn(true); + + mTile = new QuickAccessWalletTile( + mHost, + mTestableLooper.getLooper(), + new Handler(mTestableLooper.getLooper()), + new FalsingManagerFake(), + mMetricsLogger, + mStatusBarStateController, + mActivityStarter, + mQSLogger, + mQuickAccessWalletClient, + mKeyguardStateController, + mPackageManager, + mSecureSettings, + mFeatureFlags); + } + + @Test + public void testNewTile() { + assertFalse(mTile.newTileState().handlesLongClick); + } + + @Test + public void testIsAvailable_featureFlagIsOff() { + when(mFeatureFlags.isQuickAccessWalletEnabled()).thenReturn(false); + assertFalse(mTile.isAvailable()); + } + + @Test + public void testIsAvailable_qawServiceNotAvailable() { + when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false); + assertFalse(mTile.isAvailable()); + } + + @Test + public void testIsAvailable_qawServiceAvailable() { + when(mPackageManager.hasSystemFeature(FEATURE_NFC_HOST_CARD_EMULATION)).thenReturn(true); + when(mPackageManager.hasSystemFeature("org.chromium.arc")).thenReturn(false); + when(mSecureSettings.getString(NFC_PAYMENT_DEFAULT_COMPONENT)).thenReturn("Component"); + + assertTrue(mTile.isAvailable()); + } + + @Test + public void testHandleClick_openGPay() { + Intent intent = new Intent("WalletIntent"); + when(mQuickAccessWalletClient.createWalletIntent()).thenReturn(intent); + mTile.handleClick(); + + verify(mActivityStarter, times(1)) + .postStartActivityDismissingKeyguard(eq(intent), anyInt()); + } + + @Test + public void testHandleUpdateState_updateLabelAndIcon() { + QSTile.Icon icon = QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_wallet); + QSTile.State state = new QSTile.State(); + when(mQuickAccessWalletClient.getServiceLabel()).thenReturn("QuickAccessWallet"); + + mTile.handleUpdateState(state, new Object()); + + assertEquals("QuickAccessWallet", state.label.toString()); + assertTrue(state.label.toString().contentEquals(state.contentDescription)); + assertEquals(icon, state.icon); + } + + @Test + public void testHandleUpdateState_deviceLocked_tileInactive() { + QSTile.State state = new QSTile.State(); + when(mKeyguardStateController.isUnlocked()).thenReturn(false); + when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true); + + mTile.handleUpdateState(state, new Object()); + + assertEquals(Tile.STATE_INACTIVE, state.state); + assertNull(state.stateDescription); + } + + @Test + public void testHandleUpdateState_deviceLocked_tileActive() { + QSTile.State state = new QSTile.State(); + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true); + + mTile.handleUpdateState(state, new Object()); + + assertEquals(Tile.STATE_ACTIVE, state.state); + assertTrue(state.secondaryLabel.toString().contentEquals(state.stateDescription)); + assertEquals( + getContext().getString(R.string.wallet_secondary_label), + state.secondaryLabel.toString()); + } + + @Test + public void testHandleUpdateState_qawFeatureUnavailable_tileUnavailable() { + QSTile.State state = new QSTile.State(); + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(false); + + mTile.handleUpdateState(state, new Object()); + + assertEquals(Tile.STATE_UNAVAILABLE, state.state); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index b1f1b5e78b5c..116e1b98debd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -917,11 +917,11 @@ public class ScrimControllerTest extends SysuiTestCase { HashSet<ScrimState> regularStates = new HashSet<>(Arrays.asList( ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, ScrimState.BOUNCER, ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR, ScrimState.UNLOCKED, - ScrimState.BUBBLE_EXPANDED, ScrimState.SHADE_LOCKED)); + ScrimState.BUBBLE_EXPANDED, ScrimState.SHADE_LOCKED, ScrimState.AUTH_SCRIMMED)); for (ScrimState state : ScrimState.values()) { if (!lowPowerModeStates.contains(state) && !regularStates.contains(state)) { - Assert.fail("Scrim state not whitelisted nor blacklisted as low power mode"); + Assert.fail("Scrim state isn't categorized as a low power or regular state."); } } } diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 5dd271c9dbb1..f06a94004110 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -340,5 +340,9 @@ message SystemMessage { // Notify the user that window magnification is available. // package: android NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE = 1004; + + // Notify the user that some accessibility service has view and control permissions. + // package: android + NOTE_A11Y_VIEW_AND_CONTROL_ACCESS = 1005; } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index b3be0448edaf..e7ffb1a64d4f 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -309,13 +309,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ public AccessibilityManagerService(Context context) { mContext = context; - mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mPowerManager = context.getSystemService(PowerManager.class); mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); mA11yController = mWindowManagerService.getAccessibilityController(); mMainHandler = new MainHandler(mContext.getMainLooper()); mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); mPackageManager = mContext.getPackageManager(); - mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this); + PolicyWarningUIController policyWarningUIController; + if (AccessibilitySecurityPolicy.POLICY_WARNING_ENABLED) { + policyWarningUIController = new PolicyWarningUIController(mMainHandler, context, + new PolicyWarningUIController.NotificationController(context)); + } + mSecurityPolicy = new AccessibilitySecurityPolicy(policyWarningUIController, mContext, + this); mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler, mWindowManagerService, this, mSecurityPolicy, this); mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler); @@ -351,6 +357,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (isA11yTracingEnabled()) { logTrace(LOG_TAG + ".onServiceInfoChangedLocked", "userState=" + userState); } + mSecurityPolicy.onBoundServicesChangedLocked(userState.mUserId, + userState.mBoundServices); scheduleNotifyClientsOfServicesStateChangeLocked(userState); } @@ -1302,6 +1310,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityUserState userState = getCurrentUserStateLocked(); readConfigurationForUserStateLocked(userState); + mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices); // Even if reading did not yield change, we have to update // the state since the context in which the current user // state was used has changed since it was inactive. @@ -3665,6 +3674,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } else if (mEnabledAccessibilityServicesUri.equals(uri)) { if (readEnabledAccessibilityServicesLocked(userState)) { + mSecurityPolicy.onEnabledServicesChangedLocked(userState.mUserId, + userState.mEnabledServices); userState.updateCrashedServicesIfNeededLocked(); onUserStateChangedLocked(userState); } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java index bef6d3e950c1..fd355d8da341 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java @@ -41,21 +41,19 @@ import com.android.internal.util.ArrayUtils; import libcore.util.EmptyArray; +import java.util.ArrayList; +import java.util.Set; + /** * This class provides APIs of accessibility security policies for accessibility manager - * to grant accessibility capabilities or events access right to accessibility service. + * to grant accessibility capabilities or events access right to accessibility services. And also + * monitors the current bound accessibility services to prompt permission warnings for + * not accessibility-categorized ones. */ public class AccessibilitySecurityPolicy { private static final int OWN_PROCESS_ID = android.os.Process.myPid(); private static final String LOG_TAG = "AccessibilitySecurityPolicy"; - private final Context mContext; - private final PackageManager mPackageManager; - private final UserManager mUserManager; - private final AppOpsManager mAppOpsManager; - - private AppWidgetManagerInternal mAppWidgetService; - private static final int KEEP_SOURCE_EVENT_TYPES = AccessibilityEvent.TYPE_VIEW_CLICKED | AccessibilityEvent.TYPE_VIEW_FOCUSED | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER @@ -72,6 +70,8 @@ public class AccessibilitySecurityPolicy { | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY; + public static final boolean POLICY_WARNING_ENABLED = true; + /** * Methods that should find their way into separate modules, but are still in AMS * TODO (b/111889696): Refactoring UserState to AccessibilityUserManager. @@ -84,19 +84,32 @@ public class AccessibilitySecurityPolicy { // TODO: Should include resolveProfileParentLocked, but that was already in SecurityPolicy } + private final Context mContext; + private final PackageManager mPackageManager; + private final UserManager mUserManager; + private final AppOpsManager mAppOpsManager; private final AccessibilityUserManager mAccessibilityUserManager; + private final PolicyWarningUIController mPolicyWarningUIController; + /** All bound accessibility services which don't belong to accessibility category. */ + private final ArraySet<ComponentName> mNonA11yCategoryServices = new ArraySet<>(); + + private AppWidgetManagerInternal mAppWidgetService; private AccessibilityWindowManager mAccessibilityWindowManager; + private int mCurrentUserId = UserHandle.USER_NULL; /** * Constructor for AccessibilityManagerService. */ - public AccessibilitySecurityPolicy(@NonNull Context context, + public AccessibilitySecurityPolicy(PolicyWarningUIController policyWarningUIController, + @NonNull Context context, @NonNull AccessibilityUserManager a11yUserManager) { mContext = context; mAccessibilityUserManager = a11yUserManager; mPackageManager = mContext.getPackageManager(); mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + mPolicyWarningUIController = policyWarningUIController; + mPolicyWarningUIController.setAccessibilityPolicyManager(this); } /** @@ -568,4 +581,98 @@ public class AccessibilitySecurityPolicy { + permission); } } + + /** + * Called after a service was bound or unbound. Checks the current bound accessibility + * services and updates alarms. + * + * @param userId The user id + * @param boundServices The bound services + */ + public void onBoundServicesChangedLocked(int userId, + ArrayList<AccessibilityServiceConnection> boundServices) { + if (!POLICY_WARNING_ENABLED) { + return; + } + if (mAccessibilityUserManager.getCurrentUserIdLocked() != userId) { + return; + } + + ArraySet<ComponentName> tempNonA11yCategoryServices = new ArraySet<>(); + for (int i = 0; i < boundServices.size(); i++) { + final AccessibilityServiceInfo a11yServiceInfo = boundServices.get( + i).getServiceInfo(); + final ComponentName service = a11yServiceInfo.getComponentName().clone(); + if (!isA11yCategoryService(a11yServiceInfo)) { + tempNonA11yCategoryServices.add(service); + if (mNonA11yCategoryServices.contains(service)) { + mNonA11yCategoryServices.remove(service); + } else { + mPolicyWarningUIController.onNonA11yCategoryServiceBound(userId, service); + } + } + } + + for (int i = 0; i < mNonA11yCategoryServices.size(); i++) { + final ComponentName service = mNonA11yCategoryServices.valueAt(i); + mPolicyWarningUIController.onNonA11yCategoryServiceUnbound(userId, service); + } + mNonA11yCategoryServices.clear(); + mNonA11yCategoryServices.addAll(tempNonA11yCategoryServices); + } + + /** + * Called after switching to another user. Resets data and cancels old alarms after + * switching to another user. + * + * @param userId The user id + * @param enabledServices The enabled services + */ + public void onSwitchUserLocked(int userId, Set<ComponentName> enabledServices) { + if (!POLICY_WARNING_ENABLED) { + return; + } + if (mCurrentUserId == userId) { + return; + } + + mPolicyWarningUIController.onSwitchUserLocked(userId, enabledServices); + + for (int i = 0; i < mNonA11yCategoryServices.size(); i++) { + mPolicyWarningUIController.onNonA11yCategoryServiceUnbound(mCurrentUserId, + mNonA11yCategoryServices.valueAt(i)); + } + mNonA11yCategoryServices.clear(); + mCurrentUserId = userId; + } + + /** + * Called after the enabled accessibility services changed. + * + * @param userId The user id + * @param enabledServices The enabled services + */ + public void onEnabledServicesChangedLocked(int userId, + Set<ComponentName> enabledServices) { + if (!POLICY_WARNING_ENABLED) { + return; + } + if (mAccessibilityUserManager.getCurrentUserIdLocked() != userId) { + return; + } + + mPolicyWarningUIController.onEnabledServicesChangedLocked(userId, enabledServices); + } + + /** + * Identifies whether the accessibility service is true and designed for accessibility. An + * accessibility service is considered as accessibility category if + * {@link AccessibilityServiceInfo#isAccessibilityTool} is true. + * + * @param serviceInfo The accessibility service's serviceInfo. + * @return Returns true if it is a true accessibility service. + */ + public boolean isA11yCategoryService(AccessibilityServiceInfo serviceInfo) { + return serviceInfo.isAccessibilityTool(); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/PolicyWarningUIController.java b/services/accessibility/java/com/android/server/accessibility/PolicyWarningUIController.java new file mode 100644 index 000000000000..ea3e650a564a --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/PolicyWarningUIController.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility; + +import static android.app.AlarmManager.RTC_WAKEUP; + +import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_VIEW_AND_CONTROL_ACCESS; +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; + +import android.Manifest; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.app.ActivityOptions; +import android.app.AlarmManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.StatusBarManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.ArraySet; +import android.view.accessibility.AccessibilityManager; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.util.ImageUtils; + +import java.util.Calendar; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +/** + * The class handles permission warning notifications for not accessibility-categorized + * accessibility services from {@link AccessibilitySecurityPolicy}. And also maintains the setting + * {@link Settings.Secure#NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES} in order not to + * resend notifications to the same service. + */ +public class PolicyWarningUIController { + private static final String TAG = PolicyWarningUIController.class.getSimpleName(); + @VisibleForTesting + protected static final String ACTION_SEND_NOTIFICATION = TAG + ".ACTION_SEND_NOTIFICATION"; + @VisibleForTesting + protected static final String ACTION_A11Y_SETTINGS = TAG + ".ACTION_A11Y_SETTINGS"; + @VisibleForTesting + protected static final String ACTION_DISMISS_NOTIFICATION = + TAG + ".ACTION_DISMISS_NOTIFICATION"; + private static final int SEND_NOTIFICATION_DELAY_HOURS = 24; + + /** Current enabled accessibility services. */ + private final ArraySet<ComponentName> mEnabledA11yServices = new ArraySet<>(); + + private final Handler mMainHandler; + private final AlarmManager mAlarmManager; + private final Context mContext; + private final NotificationController mNotificationController; + + public PolicyWarningUIController(@NonNull Handler handler, @NonNull Context context, + NotificationController notificationController) { + mMainHandler = handler; + mContext = context; + mNotificationController = notificationController; + mAlarmManager = mContext.getSystemService(AlarmManager.class); + final IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_SEND_NOTIFICATION); + filter.addAction(ACTION_A11Y_SETTINGS); + filter.addAction(ACTION_DISMISS_NOTIFICATION); + mContext.registerReceiver(mNotificationController, filter, + Manifest.permission.MANAGE_ACCESSIBILITY, mMainHandler); + + } + + protected void setAccessibilityPolicyManager( + AccessibilitySecurityPolicy accessibilitySecurityPolicy) { + mNotificationController.setAccessibilityPolicyManager(accessibilitySecurityPolicy); + } + + /** + * Updates enabled accessibility services and notified accessibility services after switching + * to another user. + * + * @param enabledServices The current enabled services + */ + public void onSwitchUserLocked(int userId, Set<ComponentName> enabledServices) { + mEnabledA11yServices.clear(); + mEnabledA11yServices.addAll(enabledServices); + mMainHandler.sendMessage(obtainMessage(mNotificationController::onSwitchUser, userId)); + } + + /** + * Computes the newly disabled services and removes its record from the setting + * {@link Settings.Secure#NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES} after detecting the + * setting {@link Settings.Secure#ENABLED_ACCESSIBILITY_SERVICES} changed. + * + * @param userId The user id + * @param enabledServices The enabled services + */ + public void onEnabledServicesChangedLocked(int userId, + Set<ComponentName> enabledServices) { + final ArraySet<ComponentName> disabledServices = new ArraySet<>(mEnabledA11yServices); + disabledServices.removeAll(enabledServices); + mEnabledA11yServices.clear(); + mEnabledA11yServices.addAll(enabledServices); + mMainHandler.sendMessage( + obtainMessage(mNotificationController::onServicesDisabled, userId, + disabledServices)); + } + + /** + * Called when the target service is bound. Sets an 24 hours alarm to the service which is not + * notified yet to execute action {@code ACTION_SEND_NOTIFICATION}. + * + * @param userId The user id + * @param service The service's component name + */ + public void onNonA11yCategoryServiceBound(int userId, ComponentName service) { + mMainHandler.sendMessage(obtainMessage(this::setAlarm, userId, service)); + } + + /** + * Called when the target service is unbound. Cancels the old alarm with intent action + * {@code ACTION_SEND_NOTIFICATION} from the service. + * + * @param userId The user id + * @param service The service's component name + */ + public void onNonA11yCategoryServiceUnbound(int userId, ComponentName service) { + mMainHandler.sendMessage(obtainMessage(this::cancelAlarm, userId, service)); + } + + private void setAlarm(int userId, ComponentName service) { + final Calendar cal = Calendar.getInstance(); + cal.add(Calendar.HOUR, SEND_NOTIFICATION_DELAY_HOURS); + mAlarmManager.set(RTC_WAKEUP, cal.getTimeInMillis(), + createPendingIntent(mContext, userId, ACTION_SEND_NOTIFICATION, + service.flattenToShortString())); + } + + private void cancelAlarm(int userId, ComponentName service) { + mAlarmManager.cancel( + createPendingIntent(mContext, userId, ACTION_SEND_NOTIFICATION, + service.flattenToShortString())); + } + + protected static PendingIntent createPendingIntent(Context context, int userId, String action, + String serviceComponentName) { + final Intent intent = new Intent(action); + intent.setPackage(context.getPackageName()) + .setIdentifier(serviceComponentName) + .putExtra(Intent.EXTRA_USER_ID, userId); + return PendingIntent.getBroadcast(context, 0, intent, + PendingIntent.FLAG_IMMUTABLE); + } + + /** A sub class to handle notifications and settings on the main thread. */ + @MainThread + public static class NotificationController extends BroadcastReceiver { + private static final char RECORD_SEPARATOR = ':'; + + /** All accessibility services which are notified to the user by the policy warning rule. */ + private final ArraySet<ComponentName> mNotifiedA11yServices = new ArraySet<>(); + private final NotificationManager mNotificationManager; + private final Context mContext; + + private int mCurrentUserId; + private AccessibilitySecurityPolicy mAccessibilitySecurityPolicy; + + public NotificationController(Context context) { + mContext = context; + mNotificationManager = mContext.getSystemService(NotificationManager.class); + } + + protected void setAccessibilityPolicyManager( + AccessibilitySecurityPolicy accessibilitySecurityPolicy) { + mAccessibilitySecurityPolicy = accessibilitySecurityPolicy; + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + final String service = intent.getIdentifier(); + final ComponentName componentName = ComponentName.unflattenFromString(service); + if (TextUtils.isEmpty(action) || TextUtils.isEmpty(service) + || componentName == null) { + return; + } + final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_SYSTEM); + if (ACTION_SEND_NOTIFICATION.equals(action)) { + trySendNotification(userId, componentName); + } else if (ACTION_A11Y_SETTINGS.equals(action)) { + launchSettings(userId, componentName); + mNotificationManager.cancel(service, NOTE_A11Y_VIEW_AND_CONTROL_ACCESS); + onNotificationCanceled(userId, componentName); + } else if (ACTION_DISMISS_NOTIFICATION.equals(action)) { + onNotificationCanceled(userId, componentName); + } + } + + protected void onSwitchUser(int userId) { + mCurrentUserId = userId; + mNotifiedA11yServices.clear(); + mNotifiedA11yServices.addAll(readNotifiedServiceList(userId)); + } + + protected void onServicesDisabled(int userId, + ArraySet<ComponentName> disabledServices) { + if (mNotifiedA11yServices.removeAll(disabledServices)) { + writeNotifiedServiceList(userId, mNotifiedA11yServices); + } + } + + private void trySendNotification(int userId, ComponentName componentName) { + if (!AccessibilitySecurityPolicy.POLICY_WARNING_ENABLED) { + return; + } + if (userId != mCurrentUserId) { + return; + } + + List<AccessibilityServiceInfo> enabledServiceInfos = getEnabledServiceInfos(); + for (int i = 0; i < enabledServiceInfos.size(); i++) { + final AccessibilityServiceInfo a11yServiceInfo = enabledServiceInfos.get(i); + if (componentName.flattenToShortString().equals( + a11yServiceInfo.getComponentName().flattenToShortString())) { + if (!mAccessibilitySecurityPolicy.isA11yCategoryService(a11yServiceInfo) + && !mNotifiedA11yServices.contains(componentName)) { + final CharSequence displayName = + a11yServiceInfo.getResolveInfo().serviceInfo.loadLabel( + mContext.getPackageManager()); + final Drawable drawable = a11yServiceInfo.getResolveInfo().loadIcon( + mContext.getPackageManager()); + final int size = mContext.getResources().getDimensionPixelSize( + android.R.dimen.app_icon_size); + sendNotification(userId, componentName.flattenToShortString(), + displayName, + ImageUtils.buildScaledBitmap(drawable, size, size)); + } + break; + } + } + } + + private void launchSettings(int userId, ComponentName componentName) { + if (userId != mCurrentUserId) { + return; + } + final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.putExtra(Intent.EXTRA_COMPONENT_NAME, componentName.flattenToShortString()); + final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId( + mContext.getDisplayId()).toBundle(); + mContext.startActivityAsUser(intent, bundle, UserHandle.of(userId)); + mContext.getSystemService(StatusBarManager.class).collapsePanels(); + } + + protected void onNotificationCanceled(int userId, ComponentName componentName) { + if (userId != mCurrentUserId) { + return; + } + + if (mNotifiedA11yServices.add(componentName)) { + writeNotifiedServiceList(userId, mNotifiedA11yServices); + } + } + + private void sendNotification(int userId, String serviceComponentName, CharSequence name, + Bitmap bitmap) { + final Notification.Builder notificationBuilder = new Notification.Builder(mContext, + SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY); + notificationBuilder.setSmallIcon(R.drawable.ic_accessibility_24dp) + .setContentTitle( + mContext.getString(R.string.view_and_control_notification_title)) + .setContentText( + mContext.getString(R.string.view_and_control_notification_content, + name)) + .setStyle(new Notification.BigTextStyle() + .bigText( + mContext.getString( + R.string.view_and_control_notification_content, + name))) + .setTicker(mContext.getString(R.string.view_and_control_notification_title)) + .setOnlyAlertOnce(true) + .setDeleteIntent( + createPendingIntent(mContext, userId, ACTION_DISMISS_NOTIFICATION, + serviceComponentName)) + .setContentIntent( + createPendingIntent(mContext, userId, ACTION_A11Y_SETTINGS, + serviceComponentName)); + if (bitmap != null) { + notificationBuilder.setLargeIcon(bitmap); + } + mNotificationManager.notify(serviceComponentName, NOTE_A11Y_VIEW_AND_CONTROL_ACCESS, + notificationBuilder.build()); + } + + private ArraySet<ComponentName> readNotifiedServiceList(int userId) { + final String notifiedServiceSetting = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES, + userId); + if (TextUtils.isEmpty(notifiedServiceSetting)) { + return new ArraySet<>(); + } + + final TextUtils.StringSplitter componentNameSplitter = + new TextUtils.SimpleStringSplitter(RECORD_SEPARATOR); + componentNameSplitter.setString(notifiedServiceSetting); + + final ArraySet<ComponentName> notifiedServices = new ArraySet<>(); + final Iterator<String> it = componentNameSplitter.iterator(); + while (it.hasNext()) { + final String componentNameString = it.next(); + final ComponentName notifiedService = ComponentName.unflattenFromString( + componentNameString); + if (notifiedService != null) { + notifiedServices.add(notifiedService); + } + } + return notifiedServices; + } + + private void writeNotifiedServiceList(int userId, ArraySet<ComponentName> services) { + StringBuilder notifiedServicesBuilder = new StringBuilder(); + for (int i = 0; i < services.size(); i++) { + if (i > 0) { + notifiedServicesBuilder.append(RECORD_SEPARATOR); + } + final ComponentName notifiedService = services.valueAt(i); + notifiedServicesBuilder.append(notifiedService.flattenToShortString()); + } + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES, + notifiedServicesBuilder.toString(), userId); + } + + @VisibleForTesting + protected List<AccessibilityServiceInfo> getEnabledServiceInfos() { + final AccessibilityManager accessibilityManager = AccessibilityManager.getInstance( + mContext); + return accessibilityManager.getEnabledAccessibilityServiceList( + AccessibilityServiceInfo.FEEDBACK_ALL_MASK); + } + } +} diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java index b15d07b0f1a9..9d4c9ebbf592 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java @@ -16,10 +16,13 @@ package com.android.server.autofill.ui; +import static android.view.inputmethod.InlineSuggestionInfo.TYPE_SUGGESTION; + import static com.android.server.autofill.Helper.sDebug; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.IntentSender; import android.service.autofill.Dataset; import android.service.autofill.FillResponse; import android.service.autofill.InlinePresentation; @@ -49,6 +52,9 @@ final class InlineSuggestionFactory { InlineSuggestionInfo.TYPE_ACTION, () -> uiCallback.authenticate(requestId, AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED), mergedInlinePresentation(inlineFillUiInfo.mInlineRequest, 0, inlineAuthentication), + createInlineSuggestionTooltip(inlineFillUiInfo.mInlineRequest, + inlineFillUiInfo, InlineSuggestionInfo.SOURCE_AUTOFILL, + response.getInlineTooltipPresentation()), uiCallback); } @@ -66,6 +72,8 @@ final class InlineSuggestionFactory { final InlineSuggestionsRequest request = inlineFillUiInfo.mInlineRequest; SparseArray<Pair<Dataset, InlineSuggestion>> response = new SparseArray<>(datasets.size()); + + boolean hasTooltip = false; for (int datasetIndex = 0; datasetIndex < datasets.size(); datasetIndex++) { final Dataset dataset = datasets.get(datasetIndex); final int fieldIndex = dataset.getFieldIds().indexOf(inlineFillUiInfo.mFocusedId); @@ -82,14 +90,25 @@ final class InlineSuggestionFactory { } final String suggestionType = - dataset.getAuthentication() == null ? InlineSuggestionInfo.TYPE_SUGGESTION + dataset.getAuthentication() == null ? TYPE_SUGGESTION : InlineSuggestionInfo.TYPE_ACTION; final int index = datasetIndex; + InlineSuggestion inlineSuggestionTooltip = null; + if (!hasTooltip) { + // Only available for first one inline suggestion tooltip. + inlineSuggestionTooltip = createInlineSuggestionTooltip(request, + inlineFillUiInfo, suggestionSource, + dataset.getFieldInlineTooltipPresentation(fieldIndex)); + if (inlineSuggestionTooltip != null) { + hasTooltip = true; + } + } InlineSuggestion inlineSuggestion = createInlineSuggestion( inlineFillUiInfo, suggestionSource, suggestionType, () -> uiCallback.autofill(dataset, index), mergedInlinePresentation(request, datasetIndex, inlinePresentation), + inlineSuggestionTooltip, uiCallback); response.append(datasetIndex, Pair.create(dataset, inlineSuggestion)); } @@ -103,11 +122,13 @@ final class InlineSuggestionFactory { @NonNull @InlineSuggestionInfo.Type String suggestionType, @NonNull Runnable onClickAction, @NonNull InlinePresentation inlinePresentation, + @Nullable InlineSuggestion tooltip, @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) { + final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo( inlinePresentation.getInlinePresentationSpec(), suggestionSource, inlinePresentation.getAutofillHints(), suggestionType, - inlinePresentation.isPinned()); + inlinePresentation.isPinned(), tooltip); return new InlineSuggestion(inlineSuggestionInfo, createInlineContentProvider(inlineFillUiInfo, inlinePresentation, @@ -135,6 +156,60 @@ final class InlineSuggestionFactory { inlinePresentation.isPinned()); } + // TODO(182306770): creates new class instead of the InlineSuggestion. + private static InlineSuggestion createInlineSuggestionTooltip( + @NonNull InlineSuggestionsRequest request, + @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo, + String suggestionSource, + @NonNull InlinePresentation tooltipPresentation) { + if (tooltipPresentation == null) { + return null; + } + + final InlinePresentationSpec spec = request.getInlineTooltipPresentationSpec(); + InlinePresentationSpec mergedSpec; + if (spec == null) { + mergedSpec = tooltipPresentation.getInlinePresentationSpec(); + } else { + mergedSpec = new InlinePresentationSpec.Builder( + tooltipPresentation.getInlinePresentationSpec().getMinSize(), + tooltipPresentation.getInlinePresentationSpec().getMaxSize()).setStyle( + spec.getStyle()).build(); + } + + InlineFillUi.InlineSuggestionUiCallback uiCallback = + new InlineFillUi.InlineSuggestionUiCallback() { + @Override + public void autofill(Dataset dataset, int datasetIndex) { + /* nothing */ + } + + @Override + public void authenticate(int requestId, int datasetIndex) { + /* nothing */ + } + + @Override + public void startIntentSender(IntentSender intentSender) { + /* nothing */ + } + + @Override + public void onError() { + Slog.w(TAG, "An error happened on the tooltip"); + } + }; + + InlinePresentation tooltipInline = new InlinePresentation(tooltipPresentation.getSlice(), + mergedSpec, false); + IInlineContentProvider tooltipContentProvider = createInlineContentProvider( + inlineFillUiInfo, tooltipInline, () -> { /* no operation */ }, uiCallback); + final InlineSuggestionInfo tooltipInlineSuggestionInfo = new InlineSuggestionInfo( + mergedSpec, suggestionSource, /* autofillHints */ null, TYPE_SUGGESTION, + /* pinned */ false, /* tooltip */ null); + return new InlineSuggestion(tooltipInlineSuggestionInfo, tooltipContentProvider); + } + private static IInlineContentProvider createInlineContentProvider( @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo, @NonNull InlinePresentation inlinePresentation, @Nullable Runnable onClickAction, diff --git a/services/core/java/com/android/server/BluetoothDeviceConfigListener.java b/services/core/java/com/android/server/BluetoothDeviceConfigListener.java index 2dcf82ff9410..611a37de70f4 100644 --- a/services/core/java/com/android/server/BluetoothDeviceConfigListener.java +++ b/services/core/java/com/android/server/BluetoothDeviceConfigListener.java @@ -17,6 +17,9 @@ package com.android.server; import android.provider.DeviceConfig; +import android.util.Slog; + +import java.util.ArrayList; /** * The BluetoothDeviceConfigListener handles system device config change callback and checks @@ -30,10 +33,12 @@ import android.provider.DeviceConfig; class BluetoothDeviceConfigListener { private static final String TAG = "BluetoothDeviceConfigListener"; - BluetoothManagerService mService; + private final BluetoothManagerService mService; + private final boolean mLogDebug; - BluetoothDeviceConfigListener(BluetoothManagerService service) { + BluetoothDeviceConfigListener(BluetoothManagerService service, boolean logDebug) { mService = service; + mLogDebug = logDebug; DeviceConfig.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_BLUETOOTH, (Runnable r) -> r.run(), @@ -47,6 +52,13 @@ class BluetoothDeviceConfigListener { if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_BLUETOOTH)) { return; } + if (mLogDebug) { + ArrayList<String> flags = new ArrayList<>(); + for (String name : properties.getKeyset()) { + flags.add(name + "='" + properties.getString(name, "") + "'"); + } + Slog.d(TAG, "onPropertiesChanged: " + String.join(",", flags)); + } boolean foundInit = false; for (String name : properties.getKeyset()) { if (name.startsWith("INIT_")) { diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index dc24ffdb936d..f0c9ba93b080 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -454,6 +454,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED) && state == BluetoothProfile.STATE_DISCONNECTED && !mBluetoothModeChangeHelper.isA2dpOrHearingAidConnected()) { + Slog.i(TAG, "Device disconnected, reactivating pending flag changes"); onInitFlagsChanged(); } } @@ -820,6 +821,35 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return enabledProfiles; } + private boolean isDeviceProvisioned() { + return Settings.Global.getInt(mContentResolver, Settings.Global.DEVICE_PROVISIONED, + 0) != 0; + } + + // Monitor change of BLE scan only mode settings. + private void registerForProvisioningStateChange() { + ContentObserver contentObserver = new ContentObserver(null) { + @Override + public void onChange(boolean selfChange) { + if (!isDeviceProvisioned()) { + if (DBG) { + Slog.d(TAG, "DEVICE_PROVISIONED setting changed, but device is not " + + "provisioned"); + } + return; + } + if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED)) { + Slog.i(TAG, "Device provisioned, reactivating pending flag changes"); + onInitFlagsChanged(); + } + } + }; + + mContentResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), false, + contentObserver); + } + // Monitor change of BLE scan only mode settings. private void registerForBleScanModeChange() { ContentObserver contentObserver = new ContentObserver(null) { @@ -1385,7 +1415,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (mBluetoothAirplaneModeListener != null) { mBluetoothAirplaneModeListener.start(mBluetoothModeChangeHelper); } - mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this); + registerForProvisioningStateChange(); + mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this, DBG); } /** @@ -2229,12 +2260,25 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } mHandler.removeMessages(MESSAGE_INIT_FLAGS_CHANGED); if (mBluetoothModeChangeHelper.isA2dpOrHearingAidConnected()) { + Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by " + + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS + + " ms due to existing connections"); + mHandler.sendEmptyMessageDelayed( + MESSAGE_INIT_FLAGS_CHANGED, + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS); + break; + } + if (!isDeviceProvisioned()) { + Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by " + + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS + + "ms because device is not provisioned"); mHandler.sendEmptyMessageDelayed( MESSAGE_INIT_FLAGS_CHANGED, DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS); break; } if (mBluetooth != null && isEnabled()) { + Slog.i(TAG, "Restarting Bluetooth due to init flag change"); restartForReason( BluetoothProtoEnums.ENABLE_DISABLE_REASON_INIT_FLAGS_CHANGED); } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 688b33e0f685..6f0007b429ac 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -17,6 +17,10 @@ package com.android.server; import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE; +import static android.content.pm.PackageManager.FEATURE_BLUETOOTH; +import static android.content.pm.PackageManager.FEATURE_WATCH; +import static android.content.pm.PackageManager.FEATURE_WIFI; +import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK; @@ -28,15 +32,30 @@ import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; +import static android.net.ConnectivityManager.TYPE_BLUETOOTH; import static android.net.ConnectivityManager.TYPE_ETHERNET; +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.ConnectivityManager.TYPE_MOBILE_CBS; +import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; +import static android.net.ConnectivityManager.TYPE_MOBILE_EMERGENCY; +import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; +import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; +import static android.net.ConnectivityManager.TYPE_MOBILE_IA; +import static android.net.ConnectivityManager.TYPE_MOBILE_IMS; +import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; +import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL; import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.ConnectivityManager.TYPE_PROXY; import static android.net.ConnectivityManager.TYPE_VPN; +import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.ConnectivityManager.TYPE_WIFI_P2P; import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.ConnectivityManager.isNetworkTypeValid; import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS; import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL; import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID; import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; +import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE; import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; @@ -53,8 +72,9 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_VPN; -import static android.net.NetworkPolicyManager.RULE_NONE; -import static android.net.NetworkPolicyManager.uidRulesToString; +import static android.net.NetworkPolicyManager.BLOCKED_REASON_NONE; +import static android.net.NetworkPolicyManager.blockedReasonsToString; +import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST; import static android.net.shared.NetworkMonitorUtils.isPrivateDnsValidationRequired; import static android.os.Process.INVALID_UID; import static android.os.Process.VPN_UID; @@ -86,6 +106,7 @@ import android.net.ConnectivityDiagnosticsManager.ConnectivityReport; import android.net.ConnectivityDiagnosticsManager.DataStallReport; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; +import android.net.ConnectivitySettingsManager; import android.net.DataStallReportParcelable; import android.net.DnsResolverServiceManager; import android.net.ICaptivePortal; @@ -96,8 +117,7 @@ import android.net.INetd; import android.net.INetworkActivityListener; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; -import android.net.INetworkPolicyListener; -import android.net.IOnSetOemNetworkPreferenceListener; +import android.net.IOnCompleteListener; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; import android.net.InetAddresses; @@ -110,13 +130,14 @@ import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; -import android.net.NetworkConfig; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkMonitorManager; import android.net.NetworkPolicyManager; +import android.net.NetworkPolicyManager.NetworkPolicyCallback; import android.net.NetworkProvider; import android.net.NetworkRequest; +import android.net.NetworkScore; import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkStackClient; @@ -191,13 +212,13 @@ import com.android.connectivity.aidl.INetworkAgent; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; -import com.android.internal.util.LocationPermissionChecker; import com.android.internal.util.MessageUtils; import com.android.modules.utils.BasicShellCommandHandler; import com.android.net.module.util.BaseNetdUnsolicitedEventListener; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult; import com.android.net.module.util.LinkPropertiesUtils.CompareResult; +import com.android.net.module.util.LocationPermissionChecker; import com.android.net.module.util.NetworkCapabilitiesUtils; import com.android.net.module.util.PermissionUtils; import com.android.server.connectivity.AutodestructReference; @@ -213,6 +234,7 @@ import com.android.server.connectivity.NetworkNotificationManager; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; import com.android.server.connectivity.NetworkRanker; import com.android.server.connectivity.PermissionMonitor; +import com.android.server.connectivity.ProfileNetworkPreferences; import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.QosCallbackTracker; import com.android.server.net.NetworkPolicyManagerInternal; @@ -309,12 +331,10 @@ public class ConnectivityService extends IConnectivityManager.Stub private volatile boolean mLockdownEnabled; /** - * Stale copy of uid rules provided by NPMS. As long as they are accessed only in internal - * handler thread, they don't need a lock. + * Stale copy of uid blocked reasons provided by NPMS. As long as they are accessed only in + * internal handler thread, they don't need a lock. */ - private SparseIntArray mUidRules = new SparseIntArray(); - /** Flag indicating if background data is restricted. */ - private boolean mRestrictBackground; + private SparseIntArray mUidBlockedReasons = new SparseIntArray(); private final Context mContext; private final ConnectivityResources mResources; @@ -488,16 +508,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // Handle private DNS validation status updates. private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38; - /** - * Used to handle onUidRulesChanged event from NetworkPolicyManagerService. - */ - private static final int EVENT_UID_RULES_CHANGED = 39; - - /** - * Used to handle onRestrictBackgroundChanged event from NetworkPolicyManagerService. - */ - private static final int EVENT_DATA_SAVER_CHANGED = 40; - /** * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has * been tested. @@ -557,8 +567,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47; /** - * used internally when setting the default networks for OemNetworkPreferences. - * obj = OemNetworkPreferences + * Used internally when setting the default networks for OemNetworkPreferences. + * obj = Pair<OemNetworkPreferences, listener> */ private static final int EVENT_SET_OEM_NETWORK_PREFERENCE = 48; @@ -568,6 +578,19 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_REPORT_NETWORK_ACTIVITY = 49; /** + * Used internally when setting a network preference for a user profile. + * obj = Pair<ProfileNetworkPreference, Listener> + */ + private static final int EVENT_SET_PROFILE_NETWORK_PREFERENCE = 50; + + /** + * Event to specify that reasons for why an uid is blocked changed. + * arg1 = uid + * arg2 = blockedReasons + */ + private static final int EVENT_UID_BLOCKED_REASON_CHANGED = 51; + + /** * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification * should be shown. */ @@ -616,11 +639,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private UserManager mUserManager; - private NetworkConfig[] mNetConfigs; - private int mNetworksDefined; - // the set of network types that can only be enabled by system/sig apps - private List mProtectedNetworks; + private List<Integer> mProtectedNetworks; private Set<String> mWolSupportedInterfaces; @@ -710,18 +730,63 @@ public class ConnectivityService extends IConnectivityManager.Stub * They are therefore not thread-safe with respect to each other. * - getNetworkForType() can be called at any time on binder threads. It is synchronized * on mTypeLists to be thread-safe with respect to a concurrent remove call. + * - getRestoreTimerForType(type) is also synchronized on mTypeLists. * - dump is thread-safe with respect to concurrent add and remove calls. */ private final ArrayList<NetworkAgentInfo> mTypeLists[]; @NonNull private final ConnectivityService mService; + // Restore timers for requestNetworkForFeature (network type -> timer in ms). Types without + // an entry have no timer (equivalent to -1). Lazily loaded. + @NonNull + private ArrayMap<Integer, Integer> mRestoreTimers = new ArrayMap<>(); + LegacyTypeTracker(@NonNull ConnectivityService service) { mService = service; mTypeLists = new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE + 1]; } - public void addSupportedType(int type) { + public void loadSupportedTypes(@NonNull Context ctx, @NonNull TelephonyManager tm) { + final PackageManager pm = ctx.getPackageManager(); + if (pm.hasSystemFeature(FEATURE_WIFI)) { + addSupportedType(TYPE_WIFI); + } + if (pm.hasSystemFeature(FEATURE_WIFI_DIRECT)) { + addSupportedType(TYPE_WIFI_P2P); + } + if (tm.isDataCapable()) { + // Telephony does not have granular support for these types: they are either all + // supported, or none is supported + addSupportedType(TYPE_MOBILE); + addSupportedType(TYPE_MOBILE_MMS); + addSupportedType(TYPE_MOBILE_SUPL); + addSupportedType(TYPE_MOBILE_DUN); + addSupportedType(TYPE_MOBILE_HIPRI); + addSupportedType(TYPE_MOBILE_FOTA); + addSupportedType(TYPE_MOBILE_IMS); + addSupportedType(TYPE_MOBILE_CBS); + addSupportedType(TYPE_MOBILE_IA); + addSupportedType(TYPE_MOBILE_EMERGENCY); + } + if (pm.hasSystemFeature(FEATURE_BLUETOOTH)) { + addSupportedType(TYPE_BLUETOOTH); + } + if (pm.hasSystemFeature(FEATURE_WATCH)) { + // TYPE_PROXY is only used on Wear + addSupportedType(TYPE_PROXY); + } + // Ethernet is often not specified in the configs, although many devices can use it via + // USB host adapters. Add it as long as the ethernet service is here. + if (ctx.getSystemService(Context.ETHERNET_SERVICE) != null) { + addSupportedType(TYPE_ETHERNET); + } + + // Always add TYPE_VPN as a supported type + addSupportedType(TYPE_VPN); + } + + private void addSupportedType(int type) { if (mTypeLists[type] != null) { throw new IllegalStateException( "legacy list for type " + type + "already initialized"); @@ -742,6 +807,35 @@ public class ConnectivityService extends IConnectivityManager.Stub return null; } + public int getRestoreTimerForType(int type) { + synchronized (mTypeLists) { + if (mRestoreTimers == null) { + mRestoreTimers = loadRestoreTimers(); + } + return mRestoreTimers.getOrDefault(type, -1); + } + } + + private ArrayMap<Integer, Integer> loadRestoreTimers() { + final String[] configs = mService.mResources.get().getStringArray( + com.android.connectivity.resources.R.array + .config_legacy_networktype_restore_timers); + final ArrayMap<Integer, Integer> ret = new ArrayMap<>(configs.length); + for (final String config : configs) { + final String[] splits = TextUtils.split(config, ","); + if (splits.length != 2) { + logwtf("Invalid restore timer token count: " + config); + continue; + } + try { + ret.put(Integer.parseInt(splits[0]), Integer.parseInt(splits[1])); + } catch (NumberFormatException e) { + logwtf("Invalid restore timer number format: " + config, e); + } + } + return ret; + } + private void maybeLogBroadcast(NetworkAgentInfo nai, DetailedState state, int type, boolean isDefaultNetwork) { if (DBG) { @@ -1154,74 +1248,22 @@ public class ConnectivityService extends IConnectivityManager.Stub mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mLocationPermissionChecker = new LocationPermissionChecker(mContext); - // To ensure uid rules are synchronized with Network Policy, register for + // To ensure uid state is synchronized with Network Policy, register for // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService // reading existing policy from disk. - mPolicyManager.registerListener(mPolicyListener); + mPolicyManager.registerNetworkPolicyCallback(null, mPolicyCallback); final PowerManager powerManager = (PowerManager) context.getSystemService( Context.POWER_SERVICE); mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mPendingIntentWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); - mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1]; - - // TODO: What is the "correct" way to do determine if this is a wifi only device? - boolean wifiOnly = mSystemProperties.getBoolean("ro.radio.noril", false); - log("wifiOnly=" + wifiOnly); - String[] naStrings = context.getResources().getStringArray( - com.android.internal.R.array.networkAttributes); - for (String naString : naStrings) { - try { - NetworkConfig n = new NetworkConfig(naString); - if (VDBG) log("naString=" + naString + " config=" + n); - if (n.type > ConnectivityManager.MAX_NETWORK_TYPE) { - loge("Error in networkAttributes - ignoring attempt to define type " + - n.type); - continue; - } - if (wifiOnly && ConnectivityManager.isNetworkTypeMobile(n.type)) { - log("networkAttributes - ignoring mobile as this dev is wifiOnly " + - n.type); - continue; - } - if (mNetConfigs[n.type] != null) { - loge("Error in networkAttributes - ignoring attempt to redefine type " + - n.type); - continue; - } - mLegacyTypeTracker.addSupportedType(n.type); - - mNetConfigs[n.type] = n; - mNetworksDefined++; - } catch(Exception e) { - // ignore it - leave the entry null - } - } - - // Forcibly add TYPE_VPN as a supported type, if it has not already been added via config. - if (mNetConfigs[TYPE_VPN] == null) { - // mNetConfigs is used only for "restore time", which isn't applicable to VPNs, so we - // don't need to add TYPE_VPN to mNetConfigs. - mLegacyTypeTracker.addSupportedType(TYPE_VPN); - mNetworksDefined++; // used only in the log() statement below. - } - - // Do the same for Ethernet, since it's often not specified in the configs, although many - // devices can use it via USB host adapters. - if (mNetConfigs[TYPE_ETHERNET] == null - && mContext.getSystemService(Context.ETHERNET_SERVICE) != null) { - mLegacyTypeTracker.addSupportedType(TYPE_ETHERNET); - mNetworksDefined++; - } - - if (VDBG) log("mNetworksDefined=" + mNetworksDefined); - - mProtectedNetworks = new ArrayList<Integer>(); + mLegacyTypeTracker.loadSupportedTypes(mContext, mTelephonyManager); + mProtectedNetworks = new ArrayList<>(); int[] protectedNetworks = context.getResources().getIntArray( com.android.internal.R.array.config_protectedNetworks); for (int p : protectedNetworks) { - if ((mNetConfigs[p] != null) && (mProtectedNetworks.contains(p) == false)) { + if (mLegacyTypeTracker.isTypeSupported(p) && !mProtectedNetworks.contains(p)) { mProtectedNetworks.add(p); } else { if (DBG) loge("Ignoring protectedNetwork " + p); @@ -1273,20 +1315,31 @@ public class ConnectivityService extends IConnectivityManager.Stub mDnsManager = new DnsManager(mContext, mDnsResolver); registerPrivateDnsSettingsCallbacks(); + // This NAI is a sentinel used to offer no service to apps that are on a multi-layer + // request that doesn't allow fallback to the default network. It should never be visible + // to apps. As such, it's not in the list of NAIs and doesn't need many of the normal + // arguments like the handler or the DnsResolver. + // TODO : remove this ; it is probably better handled with a sentinel request. mNoServiceNetwork = new NetworkAgentInfo(null, new Network(NO_SERVICE_NET_ID), new NetworkInfo(TYPE_NONE, 0, "", ""), - new LinkProperties(), new NetworkCapabilities(), 0, mContext, - null, new NetworkAgentConfig(), this, null, - null, 0, INVALID_UID, mQosCallbackTracker, mDeps); + new LinkProperties(), new NetworkCapabilities(), + new NetworkScore.Builder().setLegacyInt(0).build(), mContext, null, + new NetworkAgentConfig(), this, null, null, 0, INVALID_UID, mQosCallbackTracker, + mDeps); } private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) { + return createDefaultNetworkCapabilitiesForUidRange(new UidRange(uid, uid)); + } + + private static NetworkCapabilities createDefaultNetworkCapabilitiesForUidRange( + @NonNull final UidRange uids) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); netCap.removeCapability(NET_CAPABILITY_NOT_VPN); - netCap.setSingleUid(uid); + netCap.setUids(Collections.singleton(uids)); return netCap; } @@ -2179,53 +2232,17 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private final INetworkPolicyListener mPolicyListener = new NetworkPolicyManager.Listener() { - @Override - public void onUidRulesChanged(int uid, int uidRules) { - mHandler.sendMessage(mHandler.obtainMessage(EVENT_UID_RULES_CHANGED, uid, uidRules)); - } + private final NetworkPolicyCallback mPolicyCallback = new NetworkPolicyCallback() { @Override - public void onRestrictBackgroundChanged(boolean restrictBackground) { - // caller is NPMS, since we only register with them - if (LOGD_BLOCKED_NETWORKINFO) { - log("onRestrictBackgroundChanged(restrictBackground=" + restrictBackground + ")"); - } - mHandler.sendMessage(mHandler.obtainMessage( - EVENT_DATA_SAVER_CHANGED, restrictBackground ? 1 : 0, 0)); + public void onUidBlockedReasonChanged(int uid, int blockedReasons) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_UID_BLOCKED_REASON_CHANGED, + uid, blockedReasons)); } }; - void handleUidRulesChanged(int uid, int newRules) { - // skip update when we've already applied rules - final int oldRules = mUidRules.get(uid, RULE_NONE); - if (oldRules == newRules) return; - - maybeNotifyNetworkBlockedForNewUidRules(uid, newRules); - - if (newRules == RULE_NONE) { - mUidRules.delete(uid); - } else { - mUidRules.put(uid, newRules); - } - } - - void handleRestrictBackgroundChanged(boolean restrictBackground) { - if (mRestrictBackground == restrictBackground) return; - - final List<UidRange> blockedRanges = mVpnBlockedUidRanges; - for (final NetworkAgentInfo nai : mNetworkAgentInfos) { - final boolean curMetered = nai.networkCapabilities.isMetered(); - maybeNotifyNetworkBlocked(nai, curMetered, curMetered, mRestrictBackground, - restrictBackground, blockedRanges, blockedRanges); - } - - mRestrictBackground = restrictBackground; - } - - private boolean isUidBlockedByRules(int uid, int uidRules, boolean isNetworkMetered, - boolean isBackgroundRestricted) { - return mPolicyManager.checkUidNetworkingBlocked(uid, uidRules, isNetworkMetered, - isBackgroundRestricted); + void handleUidBlockedReasonChanged(int uid, int blockedReasons) { + maybeNotifyNetworkBlockedForNewState(uid, blockedReasons); + mUidBlockedReasons.put(uid, blockedReasons); } private boolean checkAnyPermissionOf(String... permissions) { @@ -2596,13 +2613,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } catch (RemoteException | ServiceSpecificException e) { loge("Can't set TCP buffer sizes:" + e); } - - final Integer rwndValue = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.TCP_DEFAULT_INIT_RWND, - mSystemProperties.getInt("net.tcp.default_init_rwnd", 0)); - if (rwndValue != 0) { - mSystemProperties.setTcpInitRwnd(rwndValue); - } } @Override @@ -2619,9 +2629,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // if the system property isn't set, use the value for the apn type int ret = RESTORE_DEFAULT_NETWORK_DELAY; - if ((networkType <= ConnectivityManager.MAX_NETWORK_TYPE) && - (mNetConfigs[networkType] != null)) { - ret = mNetConfigs[networkType].restoreTime; + if (mLegacyTypeTracker.isTypeSupported(networkType)) { + ret = mLegacyTypeTracker.getRestoreTimerForType(networkType); } return ret; } @@ -2707,19 +2716,16 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.decreaseIndent(); pw.println(); - pw.print("Restrict background: "); - pw.println(mRestrictBackground); - pw.println(); - pw.println("Status for known UIDs:"); pw.increaseIndent(); - final int size = mUidRules.size(); + final int size = mUidBlockedReasons.size(); for (int i = 0; i < size; i++) { // Don't crash if the array is modified while dumping in bugreports. try { - final int uid = mUidRules.keyAt(i); - final int uidRules = mUidRules.get(uid, RULE_NONE); - pw.println("UID=" + uid + " rules=" + uidRulesToString(uidRules)); + final int uid = mUidBlockedReasons.keyAt(i); + final int blockedReasons = mUidBlockedReasons.valueAt(i); + pw.println("UID=" + uid + " blockedReasons=" + + blockedReasonsToString(blockedReasons)); } catch (ArrayIndexOutOfBoundsException e) { pw.println(" ArrayIndexOutOfBoundsException"); } catch (ConcurrentModificationException e) { @@ -2949,7 +2955,7 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } case NetworkAgent.EVENT_NETWORK_SCORE_CHANGED: { - updateNetworkScore(nai, msg.arg1); + updateNetworkScore(nai, (NetworkScore) arg.second); break; } case NetworkAgent.EVENT_SET_EXPLICITLY_SELECTED: { @@ -3656,6 +3662,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mNetworkRequestInfoLogs.log("REGISTER " + nri); for (final NetworkRequest req : nri.mRequests) { mNetworkRequests.put(req, nri); + // TODO: Consider update signal strength for other types. if (req.isListen()) { for (final NetworkAgentInfo network : mNetworkAgentInfos) { if (req.networkCapabilities.hasSignalStrength() @@ -3748,18 +3755,19 @@ public class ConnectivityService extends IConnectivityManager.Stub // listen requests won't keep up a network satisfying it. If this is not a multilayer // request, return immediately. For multilayer requests, check to see if any of the // multilayer requests may have a potential satisfier. - if (!nri.isMultilayerRequest() && nri.mRequests.get(0).isListen()) { + if (!nri.isMultilayerRequest() && (nri.mRequests.get(0).isListen() + || nri.mRequests.get(0).isListenForBest())) { return false; } for (final NetworkRequest req : nri.mRequests) { // This multilayer listen request is satisfied therefore no further requests need to be // evaluated deeming this network not a potential satisfier. - if (req.isListen() && nri.getActiveRequest() == req) { + if ((req.isListen() || req.isListenForBest()) && nri.getActiveRequest() == req) { return false; } // As non-multilayer listen requests have already returned, the below would only happen // for a multilayer request therefore continue to the next request if available. - if (req.isListen()) { + if (req.isListen() || req.isListenForBest()) { continue; } // If this Network is already the highest scoring Network for a request, or if @@ -3799,8 +3807,7 @@ public class ConnectivityService extends IConnectivityManager.Stub ? mNetworkRequests.get(request) : getNriForAppRequest(request); if (nri != null) { - if (Process.SYSTEM_UID != callingUid && Process.NETWORK_STACK_UID != callingUid - && nri.mUid != callingUid) { + if (Process.SYSTEM_UID != callingUid && nri.mUid != callingUid) { log(String.format("UID %d attempted to %s for unowned request %s", callingUid, requestedOperation, nri)); return null; @@ -4514,22 +4521,24 @@ public class ConnectivityService extends IConnectivityManager.Stub handlePrivateDnsValidationUpdate( (PrivateDnsValidationUpdate) msg.obj); break; - case EVENT_UID_RULES_CHANGED: - handleUidRulesChanged(msg.arg1, msg.arg2); - break; - case EVENT_DATA_SAVER_CHANGED: - handleRestrictBackgroundChanged(toBool(msg.arg1)); + case EVENT_UID_BLOCKED_REASON_CHANGED: + handleUidBlockedReasonChanged(msg.arg1, msg.arg2); break; case EVENT_SET_REQUIRE_VPN_FOR_UIDS: handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj); break; case EVENT_SET_OEM_NETWORK_PREFERENCE: { - final Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener> arg = - (Pair<OemNetworkPreferences, - IOnSetOemNetworkPreferenceListener>) msg.obj; + final Pair<OemNetworkPreferences, IOnCompleteListener> arg = + (Pair<OemNetworkPreferences, IOnCompleteListener>) msg.obj; handleSetOemNetworkPreference(arg.first, arg.second); break; } + case EVENT_SET_PROFILE_NETWORK_PREFERENCE: { + final Pair<ProfileNetworkPreferences.Preference, IOnCompleteListener> arg = + (Pair<ProfileNetworkPreferences.Preference, IOnCompleteListener>) + msg.obj; + handleSetProfileNetworkPreference(arg.first, arg.second); + } case EVENT_REPORT_NETWORK_ACTIVITY: mNetworkActivityTracker.handleReportNetworkActivity(); break; @@ -4828,6 +4837,10 @@ public class ConnectivityService extends IConnectivityManager.Stub Log.wtf(TAG, s); } + private static void logwtf(String s, Throwable t) { + Log.wtf(TAG, s, t); + } + private static void loge(String s) { Log.e(TAG, s); } @@ -4982,8 +4995,8 @@ public class ConnectivityService extends IConnectivityManager.Stub for (final NetworkAgentInfo nai : mNetworkAgentInfos) { final boolean curMetered = nai.networkCapabilities.isMetered(); - maybeNotifyNetworkBlocked(nai, curMetered, curMetered, mRestrictBackground, - mRestrictBackground, mVpnBlockedUidRanges, newVpnBlockedUidRanges); + maybeNotifyNetworkBlocked(nai, curMetered, curMetered, + mVpnBlockedUidRanges, newVpnBlockedUidRanges); } mVpnBlockedUidRanges = newVpnBlockedUidRanges; @@ -5100,6 +5113,9 @@ public class ConnectivityService extends IConnectivityManager.Stub private void onUserRemoved(UserHandle user) { mPermissionMonitor.onUserRemoved(user); + // If there was a network preference for this user, remove it. + handleSetProfileNetworkPreference(new ProfileNetworkPreferences.Preference(user, null), + null /* listener */); if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) { handleSetOemNetworkPreference(mOemNetworkPreferences, null); } @@ -5543,8 +5559,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // request if the app changes network state. http://b/29964605 enforceMeteredApnPolicy(networkCapabilities); break; - case TRACK_BEST: - throw new UnsupportedOperationException("Not implemented yet"); + case LISTEN_FOR_BEST: + enforceAccessPermission(); + networkCapabilities = new NetworkCapabilities(networkCapabilities); + break; default: throw new IllegalArgumentException("Unsupported request type " + reqType); } @@ -5552,11 +5570,17 @@ public class ConnectivityService extends IConnectivityManager.Stub ensureSufficientPermissionsForRequest(networkCapabilities, Binder.getCallingPid(), callingUid, callingPackageName); - // Set the UID range for this request to the single UID of the requester, or to an empty - // set of UIDs if the caller has the appropriate permission and UIDs have not been set. + // Enforce FOREGROUND if the caller does not have permission to use background network. + if (reqType == LISTEN_FOR_BEST) { + restrictBackgroundRequestForCaller(networkCapabilities); + } + + // Set the UID range for this request to the single UID of the requester, unless the + // requester has the permission to specify other UIDs. // This will overwrite any allowed UIDs in the requested capabilities. Though there // are no visible methods to set the UIDs, an app could use reflection to try and get // networks for other apps so it's essential that the UIDs are overwritten. + // Also set the requester UID and package name in the request. restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities, callingUid, callingPackageName); @@ -5890,10 +5914,16 @@ public class ConnectivityService extends IConnectivityManager.Stub @GuardedBy("mBlockedAppUids") private final HashSet<Integer> mBlockedAppUids = new HashSet<>(); - // Current OEM network preferences. + // Current OEM network preferences. This object must only be written to on the handler thread. + // Since it is immutable and always non-null, other threads may read it if they only care + // about seeing a consistent object but not that it is current. @NonNull private OemNetworkPreferences mOemNetworkPreferences = new OemNetworkPreferences.Builder().build(); + // Current per-profile network preferences. This object follows the same threading rules as + // the OEM network preferences above. + @NonNull + private ProfileNetworkPreferences mProfileNetworkPreferences = new ProfileNetworkPreferences(); // The always-on request for an Internet-capable network that apps without a specific default // fall back to. @@ -6083,20 +6113,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return nai == getDefaultNetwork(); } - // TODO : remove this method. It's a stopgap measure to help sheperding a number of dependent - // changes that would conflict throughout the automerger graph. Having this method temporarily - // helps with the process of going through with all these dependent changes across the entire - // tree. - /** - * Register a new agent. {@see #registerNetworkAgent} below. - */ - public Network registerNetworkAgent(INetworkAgent na, NetworkInfo networkInfo, - LinkProperties linkProperties, NetworkCapabilities networkCapabilities, - int currentScore, NetworkAgentConfig networkAgentConfig) { - return registerNetworkAgent(na, networkInfo, linkProperties, networkCapabilities, - currentScore, networkAgentConfig, NetworkProvider.ID_NONE); - } - /** * Register a new agent with ConnectivityService to handle a network. * @@ -6107,7 +6123,7 @@ public class ConnectivityService extends IConnectivityManager.Stub * later : see {@link #updateLinkProperties}. * @param networkCapabilities the initial capabilites of this network. They can be updated * later : see {@link #updateCapabilities}. - * @param currentScore the initial score of the network. See + * @param initialScore the initial score of the network. See * {@link NetworkAgentInfo#getCurrentScore}. * @param networkAgentConfig metadata about the network. This is never updated. * @param providerId the ID of the provider owning this NetworkAgent. @@ -6115,10 +6131,12 @@ public class ConnectivityService extends IConnectivityManager.Stub */ public Network registerNetworkAgent(INetworkAgent na, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, - int currentScore, NetworkAgentConfig networkAgentConfig, int providerId) { + @NonNull NetworkScore initialScore, NetworkAgentConfig networkAgentConfig, + int providerId) { Objects.requireNonNull(networkInfo, "networkInfo must not be null"); Objects.requireNonNull(linkProperties, "linkProperties must not be null"); Objects.requireNonNull(networkCapabilities, "networkCapabilities must not be null"); + Objects.requireNonNull(initialScore, "initialScore must not be null"); Objects.requireNonNull(networkAgentConfig, "networkAgentConfig must not be null"); if (networkCapabilities.hasTransport(TRANSPORT_TEST)) { enforceAnyPermissionOf(Manifest.permission.MANAGE_TEST_NETWORKS); @@ -6130,7 +6148,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final long token = Binder.clearCallingIdentity(); try { return registerNetworkAgentInternal(na, networkInfo, linkProperties, - networkCapabilities, currentScore, networkAgentConfig, providerId, uid); + networkCapabilities, initialScore, networkAgentConfig, providerId, uid); } finally { Binder.restoreCallingIdentity(token); } @@ -6138,7 +6156,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private Network registerNetworkAgentInternal(INetworkAgent na, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, - int currentScore, NetworkAgentConfig networkAgentConfig, int providerId, int uid) { + NetworkScore currentScore, NetworkAgentConfig networkAgentConfig, int providerId, + int uid) { if (networkCapabilities.hasTransport(TRANSPORT_TEST)) { // Strictly, sanitizing here is unnecessary as the capabilities will be sanitized in // the call to mixInCapabilities below anyway, but sanitizing here means the NAI never @@ -6760,8 +6779,8 @@ public class ConnectivityService extends IConnectivityManager.Stub final boolean meteredChanged = oldMetered != newMetered; if (meteredChanged) { - maybeNotifyNetworkBlocked(nai, oldMetered, newMetered, mRestrictBackground, - mRestrictBackground, mVpnBlockedUidRanges, mVpnBlockedUidRanges); + maybeNotifyNetworkBlocked(nai, oldMetered, newMetered, + mVpnBlockedUidRanges, mVpnBlockedUidRanges); } final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) @@ -7862,7 +7881,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void updateNetworkScore(@NonNull final NetworkAgentInfo nai, final int score) { + private void updateNetworkScore(@NonNull final NetworkAgentInfo nai, final NetworkScore score) { if (VDBG || DDBG) log("updateNetworkScore for " + nai.toShortString() + " to " + score); nai.setScore(score); rematchAllNetworksAndRequests(); @@ -7884,8 +7903,8 @@ public class ConnectivityService extends IConnectivityManager.Stub final boolean metered = nai.networkCapabilities.isMetered(); boolean blocked; blocked = isUidBlockedByVpn(nri.mUid, mVpnBlockedUidRanges); - blocked |= isUidBlockedByRules(nri.mUid, mUidRules.get(nri.mUid), - metered, mRestrictBackground); + blocked |= NetworkPolicyManager.isUidBlocked( + mUidBlockedReasons.get(nri.mUid, BLOCKED_REASON_NONE), metered); callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0); } @@ -7903,16 +7922,14 @@ public class ConnectivityService extends IConnectivityManager.Stub * * @param nai The target NetworkAgentInfo. * @param oldMetered True if the previous network capabilities is metered. - * @param newRestrictBackground True if data saver is enabled. */ private void maybeNotifyNetworkBlocked(NetworkAgentInfo nai, boolean oldMetered, - boolean newMetered, boolean oldRestrictBackground, boolean newRestrictBackground, - List<UidRange> oldBlockedUidRanges, List<UidRange> newBlockedUidRanges) { + boolean newMetered, List<UidRange> oldBlockedUidRanges, + List<UidRange> newBlockedUidRanges) { for (int i = 0; i < nai.numNetworkRequests(); i++) { NetworkRequest nr = nai.requestAt(i); NetworkRequestInfo nri = mNetworkRequests.get(nr); - final int uidRules = mUidRules.get(nri.mUid); final boolean oldBlocked, newBlocked, oldVpnBlocked, newVpnBlocked; oldVpnBlocked = isUidBlockedByVpn(nri.mUid, oldBlockedUidRanges); @@ -7920,10 +7937,11 @@ public class ConnectivityService extends IConnectivityManager.Stub ? isUidBlockedByVpn(nri.mUid, newBlockedUidRanges) : oldVpnBlocked; - oldBlocked = oldVpnBlocked || isUidBlockedByRules(nri.mUid, uidRules, oldMetered, - oldRestrictBackground); - newBlocked = newVpnBlocked || isUidBlockedByRules(nri.mUid, uidRules, newMetered, - newRestrictBackground); + final int blockedReasons = mUidBlockedReasons.get(nri.mUid, BLOCKED_REASON_NONE); + oldBlocked = oldVpnBlocked || NetworkPolicyManager.isUidBlocked( + blockedReasons, oldMetered); + newBlocked = newVpnBlocked || NetworkPolicyManager.isUidBlocked( + blockedReasons, newMetered); if (oldBlocked != newBlocked) { callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED, @@ -7933,19 +7951,20 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Notify apps with a given UID of the new blocked state according to new uid rules. + * Notify apps with a given UID of the new blocked state according to new uid state. * @param uid The uid for which the rules changed. - * @param newRules The new rules to apply. + * @param blockedReasons The reasons for why an uid is blocked. */ - private void maybeNotifyNetworkBlockedForNewUidRules(int uid, int newRules) { + private void maybeNotifyNetworkBlockedForNewState(int uid, int blockedReasons) { for (final NetworkAgentInfo nai : mNetworkAgentInfos) { final boolean metered = nai.networkCapabilities.isMetered(); final boolean vpnBlocked = isUidBlockedByVpn(uid, mVpnBlockedUidRanges); final boolean oldBlocked, newBlocked; - oldBlocked = vpnBlocked || isUidBlockedByRules( - uid, mUidRules.get(uid), metered, mRestrictBackground); - newBlocked = vpnBlocked || isUidBlockedByRules( - uid, newRules, metered, mRestrictBackground); + + oldBlocked = vpnBlocked || NetworkPolicyManager.isUidBlocked( + mUidBlockedReasons.get(uid, BLOCKED_REASON_NONE), metered); + newBlocked = vpnBlocked || NetworkPolicyManager.isUidBlocked( + blockedReasons, metered); if (oldBlocked == newBlocked) { continue; } @@ -8179,7 +8198,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.NETWORK_AVOID_BAD_WIFI, null); + ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, null); } @Override @@ -8884,13 +8903,13 @@ public class ConnectivityService extends IConnectivityManager.Stub private int transportTypeToLegacyType(int type) { switch (type) { case NetworkCapabilities.TRANSPORT_CELLULAR: - return ConnectivityManager.TYPE_MOBILE; + return TYPE_MOBILE; case NetworkCapabilities.TRANSPORT_WIFI: - return ConnectivityManager.TYPE_WIFI; + return TYPE_WIFI; case NetworkCapabilities.TRANSPORT_BLUETOOTH: - return ConnectivityManager.TYPE_BLUETOOTH; + return TYPE_BLUETOOTH; case NetworkCapabilities.TRANSPORT_ETHERNET: - return ConnectivityManager.TYPE_ETHERNET; + return TYPE_ETHERNET; default: loge("Unexpected transport in transportTypeToLegacyType: " + type); } @@ -9105,6 +9124,143 @@ public class ConnectivityService extends IConnectivityManager.Stub mQosCallbackTracker.unregisterCallback(callback); } + // Network preference per-profile and OEM network preferences can't be set at the same + // time, because it is unclear what should happen if both preferences are active for + // one given UID. To make it possible, the stack would have to clarify what would happen + // in case both are active at the same time. The implementation may have to be adjusted + // to implement the resulting rules. For example, a priority could be defined between them, + // where the OEM preference would be considered less or more important than the enterprise + // preference ; this would entail implementing the priorities somehow, e.g. by doing + // UID arithmetic with UID ranges or passing a priority to netd so that the routing rules + // are set at the right level. Other solutions are possible, e.g. merging of the + // preferences for the relevant UIDs. + private static void throwConcurrentPreferenceException() { + throw new IllegalStateException("Can't set NetworkPreferenceForUser and " + + "set OemNetworkPreference at the same time"); + } + + /** + * Request that a user profile is put by default on a network matching a given preference. + * + * See the documentation for the individual preferences for a description of the supported + * behaviors. + * + * @param profile the profile concerned. + * @param preference the preference for this profile, as one of the PROFILE_NETWORK_PREFERENCE_* + * constants. + * @param listener an optional listener to listen for completion of the operation. + */ + @Override + public void setProfileNetworkPreference(@NonNull final UserHandle profile, + @ConnectivityManager.ProfileNetworkPreference final int preference, + @Nullable final IOnCompleteListener listener) { + Objects.requireNonNull(profile); + PermissionUtils.enforceNetworkStackPermission(mContext); + if (DBG) { + log("setProfileNetworkPreference " + profile + " to " + preference); + } + if (profile.getIdentifier() < 0) { + throw new IllegalArgumentException("Must explicitly specify a user handle (" + + "UserHandle.CURRENT not supported)"); + } + final UserManager um; + try { + um = mContext.createContextAsUser(profile, 0 /* flags */) + .getSystemService(UserManager.class); + } catch (IllegalStateException e) { + throw new IllegalArgumentException("Profile does not exist"); + } + if (!um.isManagedProfile()) { + throw new IllegalArgumentException("Profile must be a managed profile"); + } + // Strictly speaking, mOemNetworkPreferences should only be touched on the + // handler thread. However it is an immutable object, so reading the reference is + // safe - it's just possible the value is slightly outdated. For the final check, + // see #handleSetProfileNetworkPreference. But if this can be caught here it is a + // lot easier to understand, so opportunistically check it. + if (!mOemNetworkPreferences.isEmpty()) { + throwConcurrentPreferenceException(); + } + final NetworkCapabilities nc; + switch (preference) { + case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT: + nc = null; + break; + case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE: + final UidRange uids = UidRange.createForUser(profile); + nc = createDefaultNetworkCapabilitiesForUidRange(uids); + nc.addCapability(NET_CAPABILITY_ENTERPRISE); + nc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + break; + default: + throw new IllegalArgumentException( + "Invalid preference in setProfileNetworkPreference"); + } + mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_PROFILE_NETWORK_PREFERENCE, + new Pair<>(new ProfileNetworkPreferences.Preference(profile, nc), listener))); + } + + private void validateNetworkCapabilitiesOfProfileNetworkPreference( + @Nullable final NetworkCapabilities nc) { + if (null == nc) return; // Null caps are always allowed. It means to remove the setting. + ensureRequestableCapabilities(nc); + } + + private ArraySet<NetworkRequestInfo> createNrisFromProfileNetworkPreferences( + @NonNull final ProfileNetworkPreferences prefs) { + final ArraySet<NetworkRequestInfo> result = new ArraySet<>(); + for (final ProfileNetworkPreferences.Preference pref : prefs.preferences) { + // The NRI for a user should be comprised of two layers: + // - The request for the capabilities + // - The request for the default network, for fallback. Create an image of it to + // have the correct UIDs in it (also a request can only be part of one NRI, because + // of lookups in 1:1 associations like mNetworkRequests). + // Note that denying a fallback can be implemented simply by not adding the second + // request. + final ArrayList<NetworkRequest> nrs = new ArrayList<>(); + nrs.add(createNetworkRequest(NetworkRequest.Type.REQUEST, pref.capabilities)); + nrs.add(createDefaultRequest()); + setNetworkRequestUids(nrs, pref.capabilities.getUids()); + final NetworkRequestInfo nri = new NetworkRequestInfo(nrs); + result.add(nri); + } + return result; + } + + private void handleSetProfileNetworkPreference( + @NonNull final ProfileNetworkPreferences.Preference preference, + @Nullable final IOnCompleteListener listener) { + // setProfileNetworkPreference and setOemNetworkPreference are mutually exclusive, in + // particular because it's not clear what preference should win in case both apply + // to the same app. + // The binder call has already checked this, but as mOemNetworkPreferences is only + // touched on the handler thread, it's theoretically not impossible that it has changed + // since. + if (!mOemNetworkPreferences.isEmpty()) { + // This may happen on a device with an OEM preference set when a user is removed. + // In this case, it's safe to ignore. In particular this happens in the tests. + loge("handleSetProfileNetworkPreference, but OEM network preferences not empty"); + return; + } + + validateNetworkCapabilitiesOfProfileNetworkPreference(preference.capabilities); + + mProfileNetworkPreferences = mProfileNetworkPreferences.plus(preference); + final ArraySet<NetworkRequestInfo> nris = + createNrisFromProfileNetworkPreferences(mProfileNetworkPreferences); + replaceDefaultNetworkRequestsForPreference(nris); + // Finally, rematch. + rematchAllNetworksAndRequests(); + + if (null != listener) { + try { + listener.onComplete(); + } catch (RemoteException e) { + loge("Listener for setProfileNetworkPreference has died"); + } + } + } + private void enforceAutomotiveDevice() { final boolean isAutomotiveDevice = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); @@ -9123,17 +9279,26 @@ public class ConnectivityService extends IConnectivityManager.Stub * Calling this will overwrite the existing preference. * * @param preference {@link OemNetworkPreferences} The application network preference to be set. - * @param listener {@link ConnectivityManager.OnSetOemNetworkPreferenceListener} Listener used + * @param listener {@link ConnectivityManager.OnCompleteListener} Listener used * to communicate completion of setOemNetworkPreference(); */ @Override public void setOemNetworkPreference( @NonNull final OemNetworkPreferences preference, - @Nullable final IOnSetOemNetworkPreferenceListener listener) { + @Nullable final IOnCompleteListener listener) { enforceAutomotiveDevice(); enforceOemNetworkPreferencesPermission(); + if (!mProfileNetworkPreferences.isEmpty()) { + // Strictly speaking, mProfileNetworkPreferences should only be touched on the + // handler thread. However it is an immutable object, so reading the reference is + // safe - it's just possible the value is slightly outdated. For the final check, + // see #handleSetOemPreference. But if this can be caught here it is a + // lot easier to understand, so opportunistically check it. + throwConcurrentPreferenceException(); + } + Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); validateOemNetworkPreferences(preference); mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE, @@ -9152,11 +9317,22 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleSetOemNetworkPreference( @NonNull final OemNetworkPreferences preference, - @Nullable final IOnSetOemNetworkPreferenceListener listener) { + @Nullable final IOnCompleteListener listener) { Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); if (DBG) { log("set OEM network preferences :" + preference.toString()); } + // setProfileNetworkPreference and setOemNetworkPreference are mutually exclusive, in + // particular because it's not clear what preference should win in case both apply + // to the same app. + // The binder call has already checked this, but as mOemNetworkPreferences is only + // touched on the handler thread, it's theoretically not impossible that it has changed + // since. + if (!mProfileNetworkPreferences.isEmpty()) { + logwtf("handleSetOemPreference, but per-profile network preferences not empty"); + return; + } + final ArraySet<NetworkRequestInfo> nris = new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference); replaceDefaultNetworkRequestsForPreference(nris); diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java index d10cf4dd0505..fa3771ac70fa 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/PersistentDataBlockService.java @@ -23,6 +23,7 @@ import android.app.ActivityManager; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; +import android.os.FileUtils; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemProperties; @@ -104,6 +105,9 @@ import java.util.concurrent.TimeUnit; public class PersistentDataBlockService extends SystemService { private static final String TAG = PersistentDataBlockService.class.getSimpleName(); + private static final String GSI_SANDBOX = "/data/gsi_persistent_data"; + private static final String GSI_RUNNING_PROP = "ro.gsid.image_running"; + private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst"; private static final int HEADER_SIZE = 8; // Magic number to mark block device as adhering to the format consumed by this service @@ -128,12 +132,13 @@ public class PersistentDataBlockService extends SystemService { private static final String FLASH_LOCK_UNLOCKED = "0"; private final Context mContext; - private final String mDataBlockFile; + private final boolean mIsRunningDSU; private final Object mLock = new Object(); private final CountDownLatch mInitDoneSignal = new CountDownLatch(1); private int mAllowedUid = -1; private long mBlockDeviceSize; + private String mDataBlockFile; @GuardedBy("mLock") private boolean mIsWritable = true; @@ -142,6 +147,7 @@ public class PersistentDataBlockService extends SystemService { super(context); mContext = context; mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP); + mIsRunningDSU = SystemProperties.getBoolean(GSI_RUNNING_PROP, false); mBlockDeviceSize = -1; // Load lazily } @@ -286,14 +292,28 @@ public class PersistentDataBlockService extends SystemService { return true; } + private FileOutputStream getBlockOutputStream() throws IOException { + if (!mIsRunningDSU) { + return new FileOutputStream(new File(mDataBlockFile)); + } else { + File sandbox = new File(GSI_SANDBOX); + File realpdb = new File(SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP)); + if (!sandbox.exists()) { + FileUtils.copy(realpdb, sandbox); + mDataBlockFile = GSI_SANDBOX; + } + Slog.i(TAG, "PersistentDataBlock copy-on-write"); + return new FileOutputStream(sandbox); + } + } + private boolean computeAndWriteDigestLocked() { byte[] digest = computeDigestLocked(null); if (digest != null) { DataOutputStream outputStream; try { - outputStream = new DataOutputStream( - new FileOutputStream(new File(mDataBlockFile))); - } catch (FileNotFoundException e) { + outputStream = new DataOutputStream(getBlockOutputStream()); + } catch (IOException e) { Slog.e(TAG, "partition not available?", e); return false; } @@ -358,8 +378,8 @@ public class PersistentDataBlockService extends SystemService { private void formatPartitionLocked(boolean setOemUnlockEnabled) { DataOutputStream outputStream; try { - outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile))); - } catch (FileNotFoundException e) { + outputStream = new DataOutputStream(getBlockOutputStream()); + } catch (IOException e) { Slog.e(TAG, "partition not available?", e); return; } @@ -384,8 +404,8 @@ public class PersistentDataBlockService extends SystemService { private void doSetOemUnlockEnabledLocked(boolean enabled) { FileOutputStream outputStream; try { - outputStream = new FileOutputStream(new File(mDataBlockFile)); - } catch (FileNotFoundException e) { + outputStream = getBlockOutputStream(); + } catch (IOException e) { Slog.e(TAG, "partition not available", e); return; } @@ -461,8 +481,8 @@ public class PersistentDataBlockService extends SystemService { DataOutputStream outputStream; try { - outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile))); - } catch (FileNotFoundException e) { + outputStream = new DataOutputStream(getBlockOutputStream()); + } catch (IOException e) { Slog.e(TAG, "partition not available?", e); return -1; } @@ -547,6 +567,17 @@ public class PersistentDataBlockService extends SystemService { public void wipe() { enforceOemUnlockWritePermission(); + if (mIsRunningDSU) { + File sandbox = new File(GSI_SANDBOX); + if (sandbox.exists()) { + if (sandbox.delete()) { + mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP); + } else { + Slog.e(TAG, "Failed to wipe sandbox persistent data block"); + } + } + return; + } synchronized (mLock) { int ret = nativeWipe(mDataBlockFile); @@ -706,8 +737,8 @@ public class PersistentDataBlockService extends SystemService { private void writeDataBuffer(long offset, ByteBuffer dataBuffer) { FileOutputStream outputStream; try { - outputStream = new FileOutputStream(new File(mDataBlockFile)); - } catch (FileNotFoundException e) { + outputStream = getBlockOutputStream(); + } catch (IOException e) { Slog.e(TAG, "partition not available", e); return; } diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 140f24f7cf8f..051cd9907bee 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -64,7 +64,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; -import com.android.internal.util.LocationPermissionChecker; +import com.android.net.module.util.LocationPermissionChecker; import com.android.server.vcn.TelephonySubscriptionTracker; import com.android.server.vcn.Vcn; import com.android.server.vcn.VcnContext; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 83cbf66ecaea..0e8644a6569e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -16147,7 +16147,12 @@ public class ActivityManagerService extends IActivityManager.Stub } } - public void waitForBroadcastIdle(PrintWriter pw) { + @Override + public void waitForBroadcastIdle() { + waitForBroadcastIdle(/* printWriter= */ null); + } + + public void waitForBroadcastIdle(@Nullable PrintWriter pw) { enforceCallingPermission(permission.DUMP, "waitForBroadcastIdle()"); while (true) { boolean idle = true; @@ -16155,9 +16160,11 @@ public class ActivityManagerService extends IActivityManager.Stub for (BroadcastQueue queue : mBroadcastQueues) { if (!queue.isIdle()) { final String msg = "Waiting for queue " + queue + " to become idle..."; - pw.println(msg); - pw.println(queue.describeState()); - pw.flush(); + if (pw != null) { + pw.println(msg); + pw.println(queue.describeState()); + pw.flush(); + } Slog.v(TAG, msg); queue.cancelDeferrals(); idle = false; @@ -16167,8 +16174,10 @@ public class ActivityManagerService extends IActivityManager.Stub if (idle) { final String msg = "All broadcast queues are idle!"; - pw.println(msg); - pw.flush(); + if (pw != null) { + pw.println(msg); + pw.flush(); + } Slog.v(TAG, msg); return; } else { diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index e2086b01ec13..e74c936af02d 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -589,9 +589,9 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { Slog.w(TAG, "exception reading modem stats: " + e.getCause()); } - final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDelta; + final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas; if (mMeasuredEnergySnapshot == null || futureECRs == null) { - measuredEnergyDelta = null; + measuredEnergyDeltas = null; } else { final int voltageMv; synchronized (mStats) { @@ -610,7 +610,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { ecrs = null; } - measuredEnergyDelta = mMeasuredEnergySnapshot.updateAndGetDelta(ecrs, voltageMv); + measuredEnergyDeltas = mMeasuredEnergySnapshot.updateAndGetDelta(ecrs, voltageMv); } final long elapsedRealtime = SystemClock.elapsedRealtime(); @@ -633,10 +633,10 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } final long[] cpuClusterChargeUC; - if (measuredEnergyDelta == null) { + if (measuredEnergyDeltas == null) { cpuClusterChargeUC = null; } else { - cpuClusterChargeUC = measuredEnergyDelta.cpuClusterChargeUC; + cpuClusterChargeUC = measuredEnergyDeltas.cpuClusterChargeUC; } mStats.updateCpuTimeLocked(onBattery, onBatteryScreenOff, cpuClusterChargeUC); } @@ -650,9 +650,9 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { mStats.updateRpmStatsLocked(elapsedRealtimeUs); } - // Inform mStats about each applicable measured energy. - if (measuredEnergyDelta != null) { - final long displayChargeUC = measuredEnergyDelta.displayChargeUC; + // Inform mStats about each applicable measured energy (unless addressed elsewhere). + if (measuredEnergyDeltas != null) { + final long displayChargeUC = measuredEnergyDeltas.displayChargeUC; if (displayChargeUC != MeasuredEnergySnapshot.UNAVAILABLE) { // If updating, pass in what BatteryExternalStatsWorker thinks screenState is. mStats.updateDisplayMeasuredEnergyStatsLocked(displayChargeUC, screenState, @@ -660,19 +660,23 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } } // Inform mStats about each applicable custom energy bucket. - if (measuredEnergyDelta != null - && measuredEnergyDelta.otherTotalChargeUC != null) { + if (measuredEnergyDeltas != null + && measuredEnergyDeltas.otherTotalChargeUC != null) { // Iterate over the custom (EnergyConsumerType.OTHER) ordinals. - for (int ord = 0; ord < measuredEnergyDelta.otherTotalChargeUC.length; ord++) { - long totalEnergy = measuredEnergyDelta.otherTotalChargeUC[ord]; - SparseLongArray uidEnergies = measuredEnergyDelta.otherUidChargesUC[ord]; + for (int ord = 0; ord < measuredEnergyDeltas.otherTotalChargeUC.length; ord++) { + long totalEnergy = measuredEnergyDeltas.otherTotalChargeUC[ord]; + SparseLongArray uidEnergies = measuredEnergyDeltas.otherUidChargesUC[ord]; mStats.updateCustomMeasuredEnergyStatsLocked(ord, totalEnergy, uidEnergies); } } if (bluetoothInfo != null) { if (bluetoothInfo.isValid()) { - mStats.updateBluetoothStateLocked(bluetoothInfo, elapsedRealtime, uptime); + final long btChargeUC = measuredEnergyDeltas != null + ? measuredEnergyDeltas.bluetoothChargeUC + : MeasuredEnergySnapshot.UNAVAILABLE; + mStats.updateBluetoothStateLocked(bluetoothInfo, + btChargeUC, elapsedRealtime, uptime); } else { Slog.w(TAG, "bluetooth info is invalid: " + bluetoothInfo); } @@ -684,10 +688,10 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { if (wifiInfo != null) { if (wifiInfo.isValid()) { - // TODO: wifiEnergyDelta = measuredEnergyDelta.consumerTypeEnergyUJ - // .get(EnergyConsumerType.WIFI, MeasuredEnergySnapshot.UNAVAILABLE) - mStats.updateWifiState(extractDeltaLocked(wifiInfo) - /*, TODO: wifiEnergyDelta */, elapsedRealtime, uptime); + final long wifiChargeUC = measuredEnergyDeltas != null ? + measuredEnergyDeltas.wifiChargeUC : MeasuredEnergySnapshot.UNAVAILABLE; + mStats.updateWifiState( + extractDeltaLocked(wifiInfo), wifiChargeUC, elapsedRealtime, uptime); } else { Slog.w(TAG, "wifi info is invalid: " + wifiInfo); } @@ -820,13 +824,19 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { for (int idx = 0; idx < size; idx++) { final EnergyConsumer consumer = idToConsumer.valueAt(idx); switch (consumer.type) { + case EnergyConsumerType.BLUETOOTH: + buckets[MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH] = true; + break; + case EnergyConsumerType.CPU_CLUSTER: + buckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true; + break; case EnergyConsumerType.DISPLAY: buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON] = true; buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_DOZE] = true; buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_OTHER] = true; break; - case EnergyConsumerType.CPU_CLUSTER: - buckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true; + case EnergyConsumerType.WIFI: + buckets[MeasuredEnergyStats.POWER_BUCKET_WIFI] = true; break; } } @@ -864,13 +874,18 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } final IntArray energyConsumerIds = new IntArray(); + if ((flags & UPDATE_BT) != 0) { + addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.BLUETOOTH); + } if ((flags & UPDATE_CPU) != 0) { addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.CPU_CLUSTER); } if ((flags & UPDATE_DISPLAY) != 0) { addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY); } - // TODO: Wifi, Bluetooth, etc., go here + if ((flags & UPDATE_WIFI) != 0) { + addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.WIFI); + } if (energyConsumerIds.size() == 0) { return null; diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 6b9fc0718879..c3f97adbd9c3 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -17,6 +17,7 @@ package com.android.server.am; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; +import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE; import android.annotation.NonNull; import android.bluetooth.BluetoothActivityEnergyInfo; @@ -1927,7 +1928,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub final long elapsedRealtime = SystemClock.elapsedRealtime(); final long uptime = SystemClock.uptimeMillis(); mHandler.post(() -> { - mStats.updateWifiState(info, elapsedRealtime, uptime); + mStats.updateWifiState(info, POWER_DATA_UNAVAILABLE, elapsedRealtime, uptime); }); } } @@ -1945,7 +1946,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub final long uptime = SystemClock.uptimeMillis(); mHandler.post(() -> { synchronized (mStats) { - mStats.updateBluetoothStateLocked(info, elapsedRealtime, uptime); + mStats.updateBluetoothStateLocked( + info, POWER_DATA_UNAVAILABLE, elapsedRealtime, uptime); } }); } diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java index 9b2ca136bdfb..4c9ab63a100b 100644 --- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java +++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java @@ -41,7 +41,7 @@ public class MeasuredEnergySnapshot { private static final int MILLIVOLTS_PER_VOLT = 1000; - public static final long UNAVAILABLE = -1L; + public static final long UNAVAILABLE = android.os.BatteryStats.POWER_DATA_UNAVAILABLE; /** Map of {@link EnergyConsumer#id} to its corresponding {@link EnergyConsumer}. */ private final SparseArray<EnergyConsumer> mEnergyConsumers; @@ -109,12 +109,18 @@ public class MeasuredEnergySnapshot { /** Class for returning the relevant data calculated from the measured energy delta */ static class MeasuredEnergyDeltaData { - /** The chargeUC for {@link EnergyConsumerType#DISPLAY}. */ - public long displayChargeUC = UNAVAILABLE; + /** The chargeUC for {@link EnergyConsumerType#BLUETOOTH}. */ + public long bluetoothChargeUC = UNAVAILABLE; /** The chargeUC for {@link EnergyConsumerType#CPU_CLUSTER}s. */ public long[] cpuClusterChargeUC = null; + /** The chargeUC for {@link EnergyConsumerType#DISPLAY}. */ + public long displayChargeUC = UNAVAILABLE; + + /** The chargeUC for {@link EnergyConsumerType#WIFI}. */ + public long wifiChargeUC = UNAVAILABLE; + /** Map of {@link EnergyConsumerType#OTHER} ordinals to their total chargeUC. */ public @Nullable long[] otherTotalChargeUC = null; @@ -196,8 +202,8 @@ public class MeasuredEnergySnapshot { final long deltaChargeUC = calculateChargeConsumedUC(deltaUJ, avgVoltageMV); switch (type) { - case EnergyConsumerType.DISPLAY: - output.displayChargeUC = deltaChargeUC; + case EnergyConsumerType.BLUETOOTH: + output.bluetoothChargeUC = deltaChargeUC; break; case EnergyConsumerType.CPU_CLUSTER: @@ -207,6 +213,14 @@ public class MeasuredEnergySnapshot { output.cpuClusterChargeUC[ordinal] = deltaChargeUC; break; + case EnergyConsumerType.DISPLAY: + output.displayChargeUC = deltaChargeUC; + break; + + case EnergyConsumerType.WIFI: + output.wifiChargeUC = deltaChargeUC; + break; + case EnergyConsumerType.OTHER: if (output.otherTotalChargeUC == null) { output.otherTotalChargeUC = new long[getNumOtherOrdinals()]; @@ -215,6 +229,7 @@ public class MeasuredEnergySnapshot { output.otherTotalChargeUC[ordinal] = deltaChargeUC; output.otherUidChargesUC[ordinal] = otherUidCharges; break; + default: Slog.w(TAG, "Ignoring consumer " + consumer.name + " of unknown type " + type); diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 1122f7f4115a..109ffe38f332 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -3091,14 +3091,10 @@ public class AppOpsService extends IAppOpsService.Stub { return AppOpsManager.MODE_IGNORED; } - // This is a workaround for R QPR, new API change is not allowed. We only allow the current - // voice recognizer is also the voice interactor to noteproxy op. - final boolean isTrustVoiceServiceProxy = AppOpsManager.isTrustedVoiceServiceProxy(mContext, - proxyPackageName, code, UserHandle.getUserId(proxyUid)); final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid; final boolean isProxyTrusted = mContext.checkPermission( Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid) - == PackageManager.PERMISSION_GRANTED || isTrustVoiceServiceProxy || isSelfBlame; + == PackageManager.PERMISSION_GRANTED || isSelfBlame; final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY; @@ -3576,14 +3572,10 @@ public class AppOpsService extends IAppOpsService.Stub { return AppOpsManager.MODE_IGNORED; } - // This is a workaround for R QPR, new API change is not allowed. We only allow the current - // voice recognizer is also the voice interactor to noteproxy op. - final boolean isTrustVoiceServiceProxy = AppOpsManager.isTrustedVoiceServiceProxy(mContext, - proxyPackageName, code, UserHandle.getUserId(proxyUid)); final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid; final boolean isProxyTrusted = mContext.checkPermission( Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid) - == PackageManager.PERMISSION_GRANTED || isTrustVoiceServiceProxy || isSelfBlame; + == PackageManager.PERMISSION_GRANTED || isSelfBlame; final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY; diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 1950710a36e0..0a6847515137 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -2258,7 +2258,7 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#getDevicesForAttributes(AudioAttributes) */ public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes( @NonNull AudioAttributes attributes) { - enforceModifyAudioRoutingPermission(); + enforceQueryStateOrModifyRoutingPermission(); return getDevicesForAttributesInt(attributes); } @@ -2900,6 +2900,16 @@ public class AudioService extends IAudioService.Stub } } + private void enforceQueryStateOrModifyRoutingPermission() { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + != PackageManager.PERMISSION_GRANTED + && mContext.checkCallingOrSelfPermission(Manifest.permission.QUERY_AUDIO_STATE) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + "Missing MODIFY_AUDIO_ROUTING or QUERY_AUDIO_STATE permissions"); + } + } + /** @see AudioManager#setVolumeIndexForAttributes(attr, int, int) */ public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags, String callingPackage) { @@ -5814,7 +5824,7 @@ public class AudioService extends IAudioService.Stub public @AudioManager.DeviceVolumeBehavior int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) { // verify permissions - enforceModifyAudioRoutingPermission(); + enforceQueryStateOrModifyRoutingPermission(); // translate Java device type to native device type (for the devices masks for full / fixed) final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice( diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 285f3185abc2..5b9fa7922e9d 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -340,6 +340,20 @@ public class AuthService extends SystemService { } @Override + public void resetLockoutTimeBound(IBinder token, String opPackageName, int fromSensorId, + int userId, byte[] hardwareAuthToken) throws RemoteException { + checkInternalPermission(); + + final long identity = Binder.clearCallingIdentity(); + try { + mBiometricService.resetLockoutTimeBound(token, opPackageName, fromSensorId, userId, + hardwareAuthToken); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public CharSequence getButtonLabel( int userId, String opPackageName, diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index a88820988ef7..63e7b4b84366 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -754,7 +754,7 @@ public class BiometricService extends SystemService { } } - @Override + @Override // Binder call public void invalidateAuthenticatorIds(int userId, int fromSensorId, IInvalidationCallback callback) { checkInternalPermission(); @@ -790,6 +790,45 @@ public class BiometricService extends SystemService { } @Override // Binder call + public void resetLockoutTimeBound(IBinder token, String opPackageName, int fromSensorId, + int userId, byte[] hardwareAuthToken) { + checkInternalPermission(); + + // Check originating strength + if (!Utils.isAtLeastStrength(getSensorForId(fromSensorId).getCurrentStrength(), + Authenticators.BIOMETRIC_STRONG)) { + Slog.w(TAG, "Sensor: " + fromSensorId + " is does not meet the required strength to" + + " request resetLockout"); + return; + } + + // Request resetLockout for applicable sensors + for (BiometricSensor sensor : mSensors) { + if (sensor.id == fromSensorId) { + continue; + } + try { + final SensorPropertiesInternal props = sensor.impl + .getSensorProperties(getContext().getOpPackageName()); + final boolean supportsChallengelessHat = + props.resetLockoutRequiresHardwareAuthToken + && !props.resetLockoutRequiresChallenge; + final boolean doesNotRequireHat = !props.resetLockoutRequiresHardwareAuthToken; + + if (supportsChallengelessHat || doesNotRequireHat) { + Slog.d(TAG, "resetLockout from: " + fromSensorId + + ", for: " + sensor.id + + ", userId: " + userId); + sensor.impl.resetLockout(token, opPackageName, userId, + hardwareAuthToken); + } + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + } + + @Override // Binder call public int getCurrentStrength(int sensorId) { checkInternalPermission(); @@ -1294,6 +1333,16 @@ public class BiometricService extends SystemService { } } + @Nullable + private BiometricSensor getSensorForId(int sensorId) { + for (BiometricSensor sensor : mSensors) { + if (sensor.id == sensorId) { + return sensor; + } + } + return null; + } + private void dumpInternal(PrintWriter pw) { pw.println("Sensors:"); for (BiometricSensor sensor : mSensors) { diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index d9e21a83e45a..f4cb38738f7e 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -176,7 +176,8 @@ public class Utils { * @param requestedStrength the strength that it must meet * @return true only if the sensor is at least as strong as the requested strength */ - public static boolean isAtLeastStrength(int sensorStrength, int requestedStrength) { + public static boolean isAtLeastStrength(@Authenticators.Types int sensorStrength, + @Authenticators.Types int requestedStrength) { // Clear out any bits that are not reserved for biometric sensorStrength &= Authenticators.BIOMETRIC_MIN_STRENGTH; diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index b31a54b8b15e..9617bb09e153 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; +import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.os.RemoteException; @@ -50,6 +51,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> private final boolean mIsStrongBiometric; private final boolean mRequireConfirmation; private final ActivityTaskManager mActivityTaskManager; + private final BiometricManager mBiometricManager; @Nullable private final TaskStackListener mTaskStackListener; private final LockoutTracker mLockoutTracker; private final boolean mIsRestricted; @@ -73,6 +75,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> mOperationId = operationId; mRequireConfirmation = requireConfirmation; mActivityTaskManager = ActivityTaskManager.getInstance(); + mBiometricManager = context.getSystemService(BiometricManager.class); mTaskStackListener = taskStackListener; mLockoutTracker = lockoutTracker; mIsRestricted = restricted; @@ -207,6 +210,13 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> for (int i = 0; i < hardwareAuthToken.size(); i++) { byteToken[i] = hardwareAuthToken.get(i); } + + if (mIsStrongBiometric) { + mBiometricManager.resetLockoutTimeBound(getToken(), + getContext().getOpPackageName(), + getSensorId(), getTargetUserId(), byteToken); + } + if (isBiometricPrompt() && listener != null) { // BiometricService will add the token to keystore listener.onAuthenticationSucceeded(getSensorId(), identifier, byteToken, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java index 06b049be4501..2926260321f1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java @@ -104,4 +104,11 @@ public final class FaceAuthenticator extends IBiometricAuthenticator.Stub { public long getAuthenticatorId(int callingUserId) throws RemoteException { return mFaceService.getAuthenticatorId(mSensorId, callingUserId); } + + @Override + public void resetLockout(IBinder token, String opPackageName, int userId, + byte[] hardwareAuthToken) throws RemoteException { + mFaceService.resetLockout(token, mSensorId, userId, hardwareAuthToken, + opPackageName); + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 6dbd590df851..a74e2da30077 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -393,14 +393,13 @@ public class FaceService extends SystemService implements BiometricServiceCallba final IFaceServiceReceiver receiver, final String opPackageName) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); - if (provider == null) { - Slog.w(TAG, "Null provider for removeAll"); - return; + for (ServiceProvider provider : mServiceProviders) { + List<FaceSensorPropertiesInternal> props = provider.getSensorProperties(); + for (FaceSensorPropertiesInternal prop : props) { + provider.scheduleRemoveAll(prop.sensorId, token, userId, receiver, + opPackageName); + } } - - provider.second.scheduleRemoveAll(provider.first, token, userId, receiver, - opPackageName); } @Override // Binder call diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java index 71bac577a4a0..ce728bc7c315 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java @@ -62,6 +62,7 @@ public class FaceResetLockoutClient extends HalClientMonitor<ISession> { // Nothing to do here } + @Override public void start(@NonNull Callback callback) { super.start(callback); startHalOperation(); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 3434acbf73cc..c7d2f0f87b6c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -495,6 +495,15 @@ public class Sensor { Slog.w(mTag, "setTestHalEnabled: " + enabled); if (enabled != mTestHalEnabled) { // The framework should retrieve a new session from the HAL. + try { + if (mCurrentSession != null && mCurrentSession.mSession != null) { + // TODO(181984005): This should be scheduled instead of directly invoked + Slog.d(mTag, "Closing old session"); + mCurrentSession.mSession.close(888 /* cookie */); + } + } catch (RemoteException e) { + Slog.e(mTag, "RemoteException", e); + } mCurrentSession = null; } mTestHalEnabled = enabled; @@ -519,6 +528,11 @@ public class Sensor { proto.end(userToken); } + proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN, + mSensorProperties.resetLockoutRequiresHardwareAuthToken); + proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE, + mSensorProperties.resetLockoutRequiresChallenge); + proto.end(sensorToken); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index afe7f24edeaf..40c050f4838b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -798,6 +798,11 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { proto.end(userToken); } + proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN, + mSensorProperties.resetLockoutRequiresHardwareAuthToken); + proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE, + mSensorProperties.resetLockoutRequiresChallenge); + proto.end(sensorToken); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java index 4cdb68df70af..84aa6d9ad1f8 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java @@ -23,11 +23,11 @@ import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback; import android.hardware.biometrics.face.V1_0.OptionalBool; import android.hardware.biometrics.face.V1_0.OptionalUint64; import android.hardware.biometrics.face.V1_0.Status; -import android.os.NativeHandle; import android.os.RemoteException; import android.util.Slog; import java.util.ArrayList; +import java.util.Arrays; public class TestHal extends IBiometricsFace.Stub { private static final String TAG = "face.hidl.TestHal"; @@ -107,8 +107,12 @@ public class TestHal extends IBiometricsFace.Stub { } @Override - public int remove(int faceId) { + public int remove(int faceId) throws RemoteException { Slog.w(TAG, "remove"); + if (mCallback != null) { + mCallback.onRemoved(0 /* deviceId */, new ArrayList<Integer>(Arrays.asList(faceId)), + 0 /* userId */); + } return 0; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java index 32e9409de4b2..9e82ffcfadc6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java @@ -105,4 +105,11 @@ public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub public long getAuthenticatorId(int callingUserId) throws RemoteException { return mFingerprintService.getAuthenticatorId(mSensorId, callingUserId); } + + @Override + public void resetLockout(IBinder token, String opPackageName, int userId, + byte[] hardwareAuthToken) throws RemoteException { + mFingerprintService.resetLockout(token, mSensorId, userId, hardwareAuthToken, + opPackageName); + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 396dd5f42d4d..3f489e98d5f1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -506,13 +506,13 @@ public class FingerprintService extends SystemService implements BiometricServic final IFingerprintServiceReceiver receiver, final String opPackageName) { Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); - if (provider == null) { - Slog.w(TAG, "Null provider for removeAll"); - return; + for (ServiceProvider provider : mServiceProviders) { + List<FingerprintSensorPropertiesInternal> props = provider.getSensorProperties(); + for (FingerprintSensorPropertiesInternal prop : props) { + provider.scheduleRemoveAll(prop.sensorId, token, receiver, userId, + opPackageName); + } } - provider.second.scheduleRemoveAll(provider.first, token, receiver, userId, - opPackageName); } @Override // Binder call diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java index ddcfcad59203..adffba280c7f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java @@ -63,6 +63,12 @@ class FingerprintResetLockoutClient extends HalClientMonitor<ISession> { } @Override + public void start(@NonNull Callback callback) { + super.start(callback); + startHalOperation(); + } + + @Override protected void startHalOperation() { try { getFreshDaemon().resetLockout(mSequentialId, mHardwareAuthToken); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index a98e7db43f79..b9dee7d1e321 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -475,6 +475,15 @@ class Sensor { Slog.w(mTag, "setTestHalEnabled: " + enabled); if (enabled != mTestHalEnabled) { // The framework should retrieve a new session from the HAL. + try { + if (mCurrentSession != null && mCurrentSession.mSession != null) { + // TODO(181984005): This should be scheduled instead of directly invoked + Slog.d(mTag, "Closing old session"); + mCurrentSession.mSession.close(999 /* cookie */); + } + } catch (RemoteException e) { + Slog.e(mTag, "RemoteException", e); + } mCurrentSession = null; } mTestHalEnabled = enabled; @@ -499,6 +508,11 @@ class Sensor { proto.end(userToken); } + proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN, + mSensorProperties.resetLockoutRequiresHardwareAuthToken); + proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE, + mSensorProperties.resetLockoutRequiresChallenge); + proto.end(sensorToken); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index 50fdc2e8a856..e737677a7f53 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -527,7 +527,9 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider // Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler // thread. mHandler.post(() -> { - mLockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId); + final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext, + userId, mContext.getOpPackageName(), sensorId, mLockoutTracker); + mScheduler.scheduleClientMonitor(client); }); } @@ -759,6 +761,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider proto.end(userToken); } + proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN, + mSensorProperties.resetLockoutRequiresHardwareAuthToken); + proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE, + mSensorProperties.resetLockoutRequiresChallenge); + proto.end(sensorToken); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java new file mode 100644 index 000000000000..a39f4f8c4d7e --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 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.biometrics.sensors.fingerprint.hidl; + +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.biometrics.BiometricsProtoEnums; + +import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.sensors.BaseClientMonitor; + +/** + * Clears lockout, which is handled in the framework (and not the HAL) for the + * IBiometricsFingerprint@2.1 interface. + */ +public class FingerprintResetLockoutClient extends BaseClientMonitor { + + @NonNull final LockoutFrameworkImpl mLockoutTracker; + + public FingerprintResetLockoutClient(@NonNull Context context, int userId, + @NonNull String owner, int sensorId, @NonNull LockoutFrameworkImpl lockoutTracker) { + super(context, null /* token */, null /* listener */, userId, owner, 0 /* cookie */, + sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN, + BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); + mLockoutTracker = lockoutTracker; + } + + @Override + public void start(@NonNull Callback callback) { + super.start(callback); + mLockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */, + getTargetUserId()); + callback.onClientFinished(this, true /* success */); + } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_RESET_LOCKOUT; + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java index 14fdb507b0b1..129f6a61df04 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java @@ -93,8 +93,11 @@ public class TestHal extends IBiometricsFingerprint.Stub { } @Override - public int remove(int gid, int fid) { + public int remove(int gid, int fid) throws RemoteException { Slog.w(TAG, "Remove"); + if (mCallback != null) { + mCallback.onRemoved(0 /* deviceId */, fid, gid, 0 /* remaining */); + } return 0; } diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java index f44e0691bb9d..95915b7809f3 100644 --- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java @@ -95,4 +95,9 @@ public final class IrisAuthenticator extends IBiometricAuthenticator.Stub { public long getAuthenticatorId(int callingUserId) throws RemoteException { return 0; } + + @Override + public void resetLockout(IBinder token, String opPackageName, int userId, + byte[] hardwareAuthToken) throws RemoteException { + } }
\ No newline at end of file diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 5bf15dc70ff9..71565301e3ed 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -80,6 +80,7 @@ import com.android.server.wm.WindowManagerInternal; import java.io.IOException; import java.io.RandomAccessFile; +import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -95,21 +96,20 @@ class HostClipboardMonitor implements Runnable { private static final String PIPE_NAME = "pipe:clipboard"; private static final String PIPE_DEVICE = "/dev/qemu_pipe"; + private static byte[] createOpenHandshake() { + // String.getBytes doesn't include the null terminator, + // but the QEMU pipe device requires the pipe service name + // to be null-terminated. + + final byte[] bits = Arrays.copyOf(PIPE_NAME.getBytes(), PIPE_NAME.length() + 1); + bits[PIPE_NAME.length()] = 0; + return bits; + } + private void openPipe() { try { - // String.getBytes doesn't include the null terminator, - // but the QEMU pipe device requires the pipe service name - // to be null-terminated. - byte[] b = new byte[PIPE_NAME.length() + 1]; - b[PIPE_NAME.length()] = 0; - System.arraycopy( - PIPE_NAME.getBytes(), - 0, - b, - 0, - PIPE_NAME.length()); mPipe = new RandomAccessFile(PIPE_DEVICE, "rw"); - mPipe.write(b); + mPipe.write(createOpenHandshake()); } catch (IOException e) { try { if (mPipe != null) mPipe.close(); @@ -173,7 +173,7 @@ public class ClipboardService extends SystemService { private static final String TAG = "ClipboardService"; private static final boolean IS_EMULATOR = - SystemProperties.getBoolean("ro.kernel.qemu", false); + SystemProperties.getBoolean("ro.boot.qemu", false); // DeviceConfig properties private static final String PROPERTY_SHOW_ACCESS_NOTIFICATIONS = "show_access_notifications"; diff --git a/services/core/java/com/android/server/connectivity/MockableSystemProperties.java b/services/core/java/com/android/server/connectivity/MockableSystemProperties.java index ef767341d609..a25b89ac039a 100644 --- a/services/core/java/com/android/server/connectivity/MockableSystemProperties.java +++ b/services/core/java/com/android/server/connectivity/MockableSystemProperties.java @@ -17,7 +17,6 @@ package com.android.server.connectivity; import android.os.SystemProperties; -import android.sysprop.NetworkProperties; public class MockableSystemProperties { @@ -32,10 +31,4 @@ public class MockableSystemProperties { public boolean getBoolean(String key, boolean def) { return SystemProperties.getBoolean(key, def); } - /** - * Set net.tcp_def_init_rwnd to the tcp initial receive window size. - */ - public void setTcpInitRwnd(int value) { - NetworkProperties.tcp_init_rwnd(value); - } } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 803cc9d31c35..e44dcf5975f1 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -35,6 +35,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkMonitorManager; import android.net.NetworkRequest; +import android.net.NetworkScore; import android.net.NetworkStateSnapshot; import android.net.QosCallbackException; import android.net.QosFilter; @@ -302,8 +303,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // validated). private boolean mInactive; - // This represents the quality of the network with no clear scale. - private int mScore; + // This represents the quality of the network. + private NetworkScore mScore; // The list of NetworkRequests being satisfied by this Network. private final SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>(); @@ -338,7 +339,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { private final QosCallbackTracker mQosCallbackTracker; public NetworkAgentInfo(INetworkAgent na, Network net, NetworkInfo info, - @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, int score, Context context, + @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, + @NonNull NetworkScore score, Context context, Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd, IDnsResolver dnsResolver, int factorySerialNumber, int creatorUid, QosCallbackTracker qosCallbackTracker, ConnectivityService.Dependencies deps) { @@ -603,9 +605,9 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { } @Override - public void sendScore(int score) { - mHandler.obtainMessage(NetworkAgent.EVENT_NETWORK_SCORE_CHANGED, score, 0, - new Pair<>(NetworkAgentInfo.this, null)).sendToTarget(); + public void sendScore(@NonNull final NetworkScore score) { + mHandler.obtainMessage(NetworkAgent.EVENT_NETWORK_SCORE_CHANGED, + new Pair<>(NetworkAgentInfo.this, score)).sendToTarget(); } @Override @@ -717,6 +719,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { break; case LISTEN: + case LISTEN_FOR_BEST: case TRACK_DEFAULT: case TRACK_SYSTEM_DEFAULT: break; @@ -857,7 +860,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { return ConnectivityConstants.EXPLICITLY_SELECTED_NETWORK_SCORE; } - int score = mScore; + int score = mScore.getLegacyInt(); if (!lastValidated && !pretendValidated && !ignoreWifiUnvalidationPenalty() && !isVPN()) { score -= ConnectivityConstants.UNVALIDATED_SCORE_PENALTY; } @@ -886,7 +889,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { return getCurrentScore(true); } - public void setScore(final int score) { + public void setScore(final NetworkScore score) { mScore = score; } diff --git a/services/core/java/com/android/server/connectivity/ProfileNetworkPreferences.java b/services/core/java/com/android/server/connectivity/ProfileNetworkPreferences.java new file mode 100644 index 000000000000..dd2815d9e2e3 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/ProfileNetworkPreferences.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2021 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.connectivity; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.NetworkCapabilities; +import android.os.UserHandle; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A data class containing all the per-profile network preferences. + * + * A given profile can only have one preference. + */ +public class ProfileNetworkPreferences { + /** + * A single preference, as it applies to a given user profile. + */ + public static class Preference { + @NonNull public final UserHandle user; + // Capabilities are only null when sending an object to remove the setting for a user + @Nullable public final NetworkCapabilities capabilities; + + public Preference(@NonNull final UserHandle user, + @Nullable final NetworkCapabilities capabilities) { + this.user = user; + this.capabilities = null == capabilities ? null : new NetworkCapabilities(capabilities); + } + + /** toString */ + public String toString() { + return "[ProfileNetworkPreference user=" + user + " caps=" + capabilities + "]"; + } + } + + @NonNull public final List<Preference> preferences; + + public ProfileNetworkPreferences() { + preferences = Collections.EMPTY_LIST; + } + + private ProfileNetworkPreferences(@NonNull final List<Preference> list) { + preferences = Collections.unmodifiableList(list); + } + + /** + * Returns a new object consisting of this object plus the passed preference. + * + * If a preference already exists for the same user, it will be replaced by the passed + * preference. Passing a Preference object containing a null capabilities object is equivalent + * to (and indeed, implemented as) removing the preference for this user. + */ + public ProfileNetworkPreferences plus(@NonNull final Preference pref) { + final ArrayList<Preference> newPrefs = new ArrayList<>(); + for (final Preference existingPref : preferences) { + if (!existingPref.user.equals(pref.user)) { + newPrefs.add(existingPref); + } + } + if (null != pref.capabilities) { + newPrefs.add(pref); + } + return new ProfileNetworkPreferences(newPrefs); + } + + public boolean isEmpty() { + return preferences.isEmpty(); + } +} diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index e9c3ec392d38..ffb532ebcca4 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2923,8 +2923,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub dismissImeOnBackKeyPressed = ((vis & InputMethodService.IME_VISIBLE) != 0); break; } - mWindowManagerInternal.updateInputMethodWindowStatus(token, - (vis & InputMethodService.IME_VISIBLE) != 0, dismissImeOnBackKeyPressed); + mWindowManagerInternal.setDismissImeOnBackKeyPressed(dismissImeOnBackKeyPressed); } @BinderThread diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java index b13c307497d9..7e5e427cf0a1 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java @@ -21,7 +21,6 @@ import android.content.Context; import android.content.pm.PackageManager; import android.text.TextUtils; import android.util.ArraySet; -import android.util.Log; import android.util.Printer; import android.util.Slog; import android.view.inputmethod.InputMethodInfo; @@ -502,7 +501,7 @@ final class InputMethodSubtypeSwitchingController { public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) { if (mController == null) { if (DEBUG) { - Log.e(TAG, "mController shouldn't be null."); + Slog.e(TAG, "mController shouldn't be null."); } return; } @@ -520,7 +519,7 @@ final class InputMethodSubtypeSwitchingController { InputMethodSubtype subtype) { if (mController == null) { if (DEBUG) { - Log.e(TAG, "mController shouldn't be null."); + Slog.e(TAG, "mController shouldn't be null."); } return null; } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java index e1c011d821a7..c8c212b3109c 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java @@ -47,9 +47,11 @@ import android.util.proto.ProtoOutputStream; import com.android.server.location.ClientBrokerProto; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; @@ -205,14 +207,19 @@ public class ContextHubClientBroker extends IContextHubClient.Stub * allowed to communicate over that channel. A channel is defined to have been opened if the * client has sent or received messages from the particular nanoapp. */ - private final Map<Long, Integer> mMessageChannelNanoappIdMap = - new ConcurrentHashMap<Long, Integer>(); + private final Map<Long, Integer> mMessageChannelNanoappIdMap = new ConcurrentHashMap<>(); + + /** + * Set containing all nanoapps that have been forcefully transitioned to the denied + * authorization state (via CLI) to ensure they don't transition back to the granted state + * later if, for example, a permission check is performed due to another nanoapp + */ + private final Set<Long> mForceDeniedNapps = new HashSet<>(); /** * Map containing all nanoapps that have active auth state denial timers. */ - private final Map<Long, AuthStateDenialTimer> mNappToAuthTimerMap = - new ConcurrentHashMap<Long, AuthStateDenialTimer>(); + private final Map<Long, AuthStateDenialTimer> mNappToAuthTimerMap = new ConcurrentHashMap<>(); /** * Callback used to obtain the latest set of nanoapp permissions and verify this client has @@ -637,7 +644,8 @@ public class ContextHubClientBroker extends IContextHubClient.Stub private int updateNanoAppAuthState( long nanoAppId, List<String> nanoappPermissions, boolean gracePeriodExpired) { return updateNanoAppAuthState( - nanoAppId, nanoappPermissions, gracePeriodExpired, false /* forceDenied */); + nanoAppId, nanoappPermissions, gracePeriodExpired, + mForceDeniedNapps.contains(nanoAppId) /* forceDenied */); } /** @@ -679,6 +687,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub // any state -> DENIED if "forceDenied" is true if (forceDenied) { newAuthState = AUTHORIZATION_DENIED; + mForceDeniedNapps.add(nanoAppId); } else if (gracePeriodExpired) { if (curAuthState == AUTHORIZATION_DENIED_GRACE_PERIOD) { newAuthState = AUTHORIZATION_DENIED; diff --git a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java index 6d250ecb9fa4..6201b9414aaf 100644 --- a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java +++ b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java @@ -119,19 +119,11 @@ public class BiometricDeferredQueue { for (UserAuthInfo userAuthInfo : pendingResetLockuts) { Slog.d(TAG, "Resetting face lockout for sensor: " + sensorId + ", user: " + userAuthInfo.userId); - final VerifyCredentialResponse response = spManager.verifyChallengeInternal( - getGatekeeperService(), userAuthInfo.gatekeeperPassword, challenge, - userAuthInfo.userId); - if (response == null) { - Slog.wtf(TAG, "VerifyChallenge failed, null response"); - continue; + final byte[] hat = requestHatFromGatekeeperPassword(spManager, userAuthInfo, + challenge); + if (hat != null) { + faceManager.resetLockout(sensorId, userAuthInfo.userId, hat); } - if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) { - Slog.wtf(TAG, "VerifyChallenge failed, response: " - + response.getResponseCode()); - } - faceManager.resetLockout(sensorId, userAuthInfo.userId, - response.getGatekeeperHAT()); } sensorIds.remove(sensorId); @@ -146,15 +138,6 @@ public class BiometricDeferredQueue { finishCallback.onFinished(); } } - - synchronized IGateKeeperService getGatekeeperService() { - final IBinder service = ServiceManager.getService(Context.GATEKEEPER_SERVICE); - if (service == null) { - Slog.e(TAG, "Unable to acquire GateKeeperService"); - return null; - } - return IGateKeeperService.Stub.asInterface(service); - } } @Nullable private FaceResetLockoutTask mFaceResetLockoutTask; @@ -214,10 +197,19 @@ public class BiometricDeferredQueue { mFingerprintManager.resetLockout(prop.sensorId, user.userId, null /* hardwareAuthToken */); } + } else if (!prop.resetLockoutRequiresChallenge) { + for (UserAuthInfo user : pendingResetLockouts) { + Slog.d(TAG, "Resetting fingerprint lockout for sensor: " + prop.sensorId + + ", user: " + user.userId); + final byte[] hat = requestHatFromGatekeeperPassword(mSpManager, user, + 0 /* challenge */); + if (hat != null) { + mFingerprintManager.resetLockout(prop.sensorId, user.userId, hat); + } + } } else { - Slog.e(TAG, "Fingerprint resetLockout with HAT not supported yet"); - // TODO(b/152414803): Implement this when resetLockout is implemented below - // the framework. + Slog.w(TAG, "No fingerprint HAL interface requires HAT with challenge" + + ", sensorId: " + prop.sensorId); } } } @@ -228,11 +220,6 @@ public class BiometricDeferredQueue { * in-flight challenge, we generate a single challenge to reset lockout for all profiles. This * hopefully reduces/eliminates issues such as overwritten challenge, incorrectly revoked * challenge, or other race conditions. - * - * TODO(b/162965646) This logic can be avoided if multiple in-flight challenges are supported. - * Though it will need to continue to exist to support existing HIDLs, each profile that - * requires resetLockout could have its own challenge, and the `mPendingResetLockouts` queue - * can be avoided. */ private void processPendingLockoutsForFace(List<UserAuthInfo> pendingResetLockouts) { if (mFaceManager != null) { @@ -251,10 +238,60 @@ public class BiometricDeferredQueue { mFaceResetLockoutTask = new FaceResetLockoutTask(mFaceFinishCallback, mFaceManager, mSpManager, sensorIds, pendingResetLockouts); for (final FaceSensorPropertiesInternal prop : faceSensorProperties) { - // Generate a challenge for each sensor. The challenge does not need to be - // per-user, since the HAT returned by gatekeeper contains userId. - mFaceManager.generateChallenge(prop.sensorId, mFaceResetLockoutTask); + if (prop.resetLockoutRequiresHardwareAuthToken) { + if (prop.resetLockoutRequiresChallenge) { + // Generate a challenge for each sensor. The challenge does not need to be + // per-user, since the HAT returned by gatekeeper contains userId. + mFaceManager.generateChallenge(prop.sensorId, mFaceResetLockoutTask); + } else { + for (UserAuthInfo user : pendingResetLockouts) { + Slog.d(TAG, "Resetting face lockout for sensor: " + prop.sensorId + + ", user: " + user.userId); + final byte[] hat = requestHatFromGatekeeperPassword(mSpManager, user, + 0 /* challenge */); + if (hat != null) { + mFaceManager.resetLockout(prop.sensorId, user.userId, hat); + } + } + } + } else { + Slog.w(TAG, "Lockout is below the HAL for all face authentication interfaces" + + ", sensorId: " + prop.sensorId); + } } } } + + @Nullable + private static byte[] requestHatFromGatekeeperPassword( + @NonNull SyntheticPasswordManager spManager, + @NonNull UserAuthInfo userAuthInfo, long challenge) { + final VerifyCredentialResponse response = spManager.verifyChallengeInternal( + getGatekeeperService(), userAuthInfo.gatekeeperPassword, challenge, + userAuthInfo.userId); + if (response == null) { + Slog.wtf(TAG, "VerifyChallenge failed, null response"); + return null; + } + if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) { + Slog.wtf(TAG, "VerifyChallenge failed, response: " + + response.getResponseCode()); + return null; + } + if (response.getGatekeeperHAT() == null) { + Slog.e(TAG, "Null HAT received from spManager"); + } + + return response.getGatekeeperHAT(); + } + + @Nullable + private static synchronized IGateKeeperService getGatekeeperService() { + final IBinder service = ServiceManager.getService(Context.GATEKEEPER_SERVICE); + if (service == null) { + Slog.e(TAG, "Unable to acquire GateKeeperService"); + return null; + } + return IGateKeeperService.Stub.asInterface(service); + } } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 43c736544ee6..22a9a47c9651 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -91,7 +91,6 @@ import android.provider.Settings.SettingNotFoundException; import android.security.AndroidKeyStoreMaintenance; import android.security.Authorization; import android.security.KeyStore; -import android.security.keystore.AndroidKeyStoreProvider; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import android.security.keystore.UserNotAuthenticatedException; @@ -158,7 +157,6 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; -import java.util.Optional; import java.util.Random; import java.util.Set; import java.util.concurrent.CountDownLatch; @@ -263,13 +261,7 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public void onStart() { - Optional<Boolean> keystore2_enabled = - android.sysprop.Keystore2Properties.keystore2_enabled(); - if (keystore2_enabled.isPresent() && keystore2_enabled.get()) { - android.security.keystore2.AndroidKeyStoreProvider.install(); - } else { - AndroidKeyStoreProvider.install(); - } + android.security.keystore2.AndroidKeyStoreProvider.install(); mLockSettingsService = new LockSettingsService(getContext()); publishBinderService("lock_settings", mLockSettingsService); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index da62aca70cd3..735381873c05 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -58,6 +58,23 @@ import static android.net.NetworkIdentity.OEM_NONE; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.SNOOZE_NEVER; import static android.net.NetworkPolicy.WARNING_DISABLED; +import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_MASK; +import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_USER_EXEMPTED; +import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND; +import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE; +import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLIST; +import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST; +import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS; +import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM; +import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_ADMIN_DISABLED; +import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_DATA_SAVER; +import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_MASK; +import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_USER_RESTRICTED; +import static android.net.NetworkPolicyManager.BLOCKED_REASON_APP_STANDBY; +import static android.net.NetworkPolicyManager.BLOCKED_REASON_BATTERY_SAVER; +import static android.net.NetworkPolicyManager.BLOCKED_REASON_DOZE; +import static android.net.NetworkPolicyManager.BLOCKED_REASON_NONE; +import static android.net.NetworkPolicyManager.BLOCKED_REASON_RESTRICTED_MODE; import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS; @@ -413,6 +430,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18; private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19; private static final int MSG_STATS_PROVIDER_LIMIT_REACHED = 20; + // TODO: Add similar docs for other messages. + /** + * Message to indicate that reasons for why an uid is blocked changed. + * arg1 = uid + * arg2 = oldBlockedReasons + * obj = newBlockedReasons + */ + private static final int MSG_BLOCKED_REASON_CHANGED = 21; private static final int UID_MSG_STATE_CHANGED = 100; private static final int UID_MSG_GONE = 101; @@ -559,7 +584,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { /** Foreground at UID granularity. */ @GuardedBy("mUidRulesFirstLock") - final SparseArray<UidState> mUidState = new SparseArray<UidState>(); + private final SparseArray<UidState> mUidState = new SparseArray<>(); + + @GuardedBy("mUidRulesFirstLock") + private final SparseArray<UidBlockedState> mUidBlockedState = new SparseArray<>(); /** Map from network ID to last observed meteredness state */ @GuardedBy("mNetworkPoliciesSecondLock") @@ -2876,15 +2904,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override - public void registerListener(INetworkPolicyListener listener) { + public void registerListener(@NonNull INetworkPolicyListener listener) { + Objects.requireNonNull(listener); // TODO: Remove CONNECTIVITY_INTERNAL and the *AnyPermissionOf methods above after all apps // have declared OBSERVE_NETWORK_POLICY. enforceAnyPermissionOf(CONNECTIVITY_INTERNAL, OBSERVE_NETWORK_POLICY); mListeners.register(listener); + // TODO: Send callbacks to the newly registered listener } @Override - public void unregisterListener(INetworkPolicyListener listener) { + public void unregisterListener(@NonNull INetworkPolicyListener listener) { + Objects.requireNonNull(listener); // TODO: Remove CONNECTIVITY_INTERNAL and the *AnyPermissionOf methods above after all apps // have declared OBSERVE_NETWORK_POLICY. enforceAnyPermissionOf(CONNECTIVITY_INTERNAL, OBSERVE_NETWORK_POLICY); @@ -3921,6 +3952,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mUidRules.put(uid, newUidRule); mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRule).sendToTarget(); } + updateBlockedReasonsForRestrictedModeUL(uid); }); if (mRestrictedNetworkingMode) { // firewall rules only need to be set when this mode is being enabled. @@ -3941,6 +3973,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mUidRules.put(uid, newUidRule); mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRule).sendToTarget(); } + updateBlockedReasonsForRestrictedModeUL(uid); // if restricted networking mode is on, and the app has an access exemption, the uid rule // will not change, but the firewall rule will have to be updated. @@ -3952,6 +3985,31 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } + private void updateBlockedReasonsForRestrictedModeUL(int uid) { + UidBlockedState uidBlockedState = mUidBlockedState.get(uid); + if (uidBlockedState == null) { + uidBlockedState = new UidBlockedState(); + mUidBlockedState.put(uid, uidBlockedState); + } + final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons; + if (mRestrictedNetworkingMode) { + uidBlockedState.blockedReasons |= BLOCKED_REASON_RESTRICTED_MODE; + } else { + uidBlockedState.blockedReasons &= ~BLOCKED_REASON_RESTRICTED_MODE; + } + if (hasRestrictedModeAccess(uid)) { + uidBlockedState.allowedReasons |= ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS; + } else { + uidBlockedState.allowedReasons &= ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS; + } + uidBlockedState.updateEffectiveBlockedReasons(); + if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) { + mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid, + uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons) + .sendToTarget(); + } + } + private int getNewRestrictedModeUidRule(int uid, int oldUidRule) { int newRule = oldUidRule; newRule &= ~MASK_RESTRICTED_MODE_NETWORKS; @@ -4072,11 +4130,21 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { boolean isWhitelisted = mPowerSaveTempWhitelistAppIds.get(appId) || mPowerSaveWhitelistAppIds.get(appId); if (!deviceIdleMode) { - isWhitelisted = isWhitelisted || mPowerSaveWhitelistExceptIdleAppIds.get(appId); + isWhitelisted = isWhitelisted || isWhitelistedFromPowerSaveExceptIdleUL(uid); } return isWhitelisted; } + /** + * Returns whether a uid is allowlisted from power saving restrictions, except Device idle + * (eg: Battery Saver and app idle). + */ + @GuardedBy("mUidRulesFirstLock") + private boolean isWhitelistedFromPowerSaveExceptIdleUL(int uid) { + final int appId = UserHandle.getAppId(uid); + return mPowerSaveWhitelistExceptIdleAppIds.get(appId); + } + // NOTE: since both fw_dozable and fw_powersave uses the same map // (mPowerSaveTempWhitelistAppIds) for allowlisting, we can reuse their logic in this method. @GuardedBy("mUidRulesFirstLock") @@ -4521,6 +4589,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final int oldUidRules = mUidRules.get(uid, RULE_NONE); final boolean isForeground = isUidForegroundOnRestrictBackgroundUL(uid); final boolean isRestrictedByAdmin = isRestrictedByAdminUL(uid); + UidBlockedState uidBlockedState = mUidBlockedState.get(uid); + if (uidBlockedState == null) { + uidBlockedState = new UidBlockedState(); + mUidBlockedState.put(uid, uidBlockedState); + } final boolean isDenied = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0; final boolean isAllowed = (uidPolicy & POLICY_ALLOW_METERED_BACKGROUND) != 0; @@ -4545,6 +4618,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } + int newBlockedReasons = BLOCKED_REASON_NONE; + int newAllowedReasons = ALLOWED_REASON_NONE; + newBlockedReasons |= (isRestrictedByAdmin ? BLOCKED_METERED_REASON_ADMIN_DISABLED : 0); + newBlockedReasons |= (mRestrictBackground ? BLOCKED_METERED_REASON_DATA_SAVER : 0); + newBlockedReasons |= (isDenied ? BLOCKED_METERED_REASON_USER_RESTRICTED : 0); + + newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0); + newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0); + newAllowedReasons |= (isAllowed ? ALLOWED_METERED_REASON_USER_EXEMPTED : 0); + if (LOGV) { Log.v(TAG, "updateRuleForRestrictBackgroundUL(" + uid + ")" + ": isForeground=" +isForeground @@ -4616,6 +4699,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // Dispatch changed rule to existing listeners. mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget(); + + final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons; + uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons + & ~BLOCKED_METERED_REASON_MASK) | newBlockedReasons; + uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons + & ~ALLOWED_METERED_REASON_MASK) | newAllowedReasons; + uidBlockedState.updateEffectiveBlockedReasons(); + if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) { + mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid, + uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons) + .sendToTarget(); + } } } @@ -4690,6 +4785,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // Copy existing uid rules and clear ALL_NETWORK rules. int newUidRules = oldUidRules & (~MASK_ALL_NETWORKS); + UidBlockedState uidBlockedState = mUidBlockedState.get(uid); + if (uidBlockedState == null) { + uidBlockedState = new UidBlockedState(); + mUidBlockedState.put(uid, uidBlockedState); + } + // First step: define the new rule based on user restrictions and foreground state. // NOTE: if statements below could be inlined, but it's easier to understand the logic @@ -4702,6 +4803,20 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { newUidRules |= isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL; } + int newBlockedReasons = BLOCKED_REASON_NONE; + int newAllowedReasons = ALLOWED_REASON_NONE; + newBlockedReasons |= (mRestrictPower ? BLOCKED_REASON_BATTERY_SAVER : 0); + newBlockedReasons |= (mDeviceIdleMode ? BLOCKED_REASON_DOZE : 0); + newBlockedReasons |= (isUidIdle ? BLOCKED_REASON_APP_STANDBY : 0); + newBlockedReasons |= (uidBlockedState.blockedReasons & BLOCKED_REASON_RESTRICTED_MODE); + + newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0); + newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0); + newAllowedReasons |= (isWhitelistedFromPowerSaveUL(uid, true) + ? ALLOWED_REASON_POWER_SAVE_ALLOWLIST : 0); + newAllowedReasons |= (isWhitelistedFromPowerSaveExceptIdleUL(uid) + ? ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST : 0); + if (LOGV) { Log.v(TAG, "updateRulesForPowerRestrictionsUL(" + uid + ")" + ", isIdle: " + isUidIdle @@ -4733,6 +4848,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget(); } + final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons; + uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons + & BLOCKED_METERED_REASON_MASK) | newBlockedReasons; + uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons + & ALLOWED_METERED_REASON_MASK) | newAllowedReasons; + uidBlockedState.updateEffectiveBlockedReasons(); + if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) { + mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid, + uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons) + .sendToTarget(); + } + return newUidRules; } @@ -4762,61 +4889,57 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } private void dispatchUidRulesChanged(INetworkPolicyListener listener, int uid, int uidRules) { - if (listener != null) { - try { - listener.onUidRulesChanged(uid, uidRules); - } catch (RemoteException ignored) { - } + try { + listener.onUidRulesChanged(uid, uidRules); + } catch (RemoteException ignored) { } } private void dispatchMeteredIfacesChanged(INetworkPolicyListener listener, String[] meteredIfaces) { - if (listener != null) { - try { - listener.onMeteredIfacesChanged(meteredIfaces); - } catch (RemoteException ignored) { - } + try { + listener.onMeteredIfacesChanged(meteredIfaces); + } catch (RemoteException ignored) { } } private void dispatchRestrictBackgroundChanged(INetworkPolicyListener listener, boolean restrictBackground) { - if (listener != null) { - try { - listener.onRestrictBackgroundChanged(restrictBackground); - } catch (RemoteException ignored) { - } + try { + listener.onRestrictBackgroundChanged(restrictBackground); + } catch (RemoteException ignored) { } } private void dispatchUidPoliciesChanged(INetworkPolicyListener listener, int uid, int uidPolicies) { - if (listener != null) { - try { - listener.onUidPoliciesChanged(uid, uidPolicies); - } catch (RemoteException ignored) { - } + try { + listener.onUidPoliciesChanged(uid, uidPolicies); + } catch (RemoteException ignored) { } } private void dispatchSubscriptionOverride(INetworkPolicyListener listener, int subId, int overrideMask, int overrideValue, int[] networkTypes) { - if (listener != null) { - try { - listener.onSubscriptionOverride(subId, overrideMask, overrideValue, networkTypes); - } catch (RemoteException ignored) { - } + try { + listener.onSubscriptionOverride(subId, overrideMask, overrideValue, networkTypes); + } catch (RemoteException ignored) { } } private void dispatchSubscriptionPlansChanged(INetworkPolicyListener listener, int subId, SubscriptionPlan[] plans) { - if (listener != null) { - try { - listener.onSubscriptionPlansChanged(subId, plans); - } catch (RemoteException ignored) { - } + try { + listener.onSubscriptionPlansChanged(subId, plans); + } catch (RemoteException ignored) { + } + } + + private void dispatchBlockedReasonChanged(INetworkPolicyListener listener, int uid, + int oldBlockedReasons, int newBlockedReasons) { + try { + listener.onBlockedReasonChanged(uid, oldBlockedReasons, newBlockedReasons); + } catch (RemoteException ignored) { } } @@ -4973,6 +5096,19 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mListeners.finishBroadcast(); return true; } + case MSG_BLOCKED_REASON_CHANGED: { + final int uid = msg.arg1; + final int newBlockedReasons = msg.arg2; + final int oldBlockedReasons = (int) msg.obj; + final int length = mListeners.beginBroadcast(); + for (int i = 0; i < length; i++) { + final INetworkPolicyListener listener = mListeners.getBroadcastItem(i); + dispatchBlockedReasonChanged(listener, uid, + oldBlockedReasons, newBlockedReasons); + } + mListeners.finishBroadcast(); + return true; + } default: { return false; } @@ -5704,6 +5840,51 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return (bundle != null) ? bundle.getBoolean(key, defaultValue) : defaultValue; } + private class UidBlockedState { + public int blockedReasons; + public int allowedReasons; + public int effectiveBlockedReasons; + + UidBlockedState() { + blockedReasons = BLOCKED_REASON_NONE; + allowedReasons = ALLOWED_REASON_NONE; + effectiveBlockedReasons = BLOCKED_REASON_NONE; + } + + void updateEffectiveBlockedReasons() { + effectiveBlockedReasons = blockedReasons; + // If the uid is not subject to any blocked reasons, then return early + if (blockedReasons == BLOCKED_REASON_NONE) { + return; + } + if ((allowedReasons & ALLOWED_REASON_SYSTEM) != 0) { + effectiveBlockedReasons = BLOCKED_REASON_NONE; + } + if ((allowedReasons & ALLOWED_REASON_FOREGROUND) != 0) { + effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER; + effectiveBlockedReasons &= ~BLOCKED_REASON_DOZE; + effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY; + effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_DATA_SAVER; + effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_USER_RESTRICTED; + } + if ((allowedReasons & ALLOWED_REASON_POWER_SAVE_ALLOWLIST) != 0) { + effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER; + effectiveBlockedReasons &= ~BLOCKED_REASON_DOZE; + effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY; + } + if ((allowedReasons & ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST) != 0) { + effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER; + effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY; + } + if ((allowedReasons & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS) != 0) { + effectiveBlockedReasons &= ~BLOCKED_REASON_RESTRICTED_MODE; + } + if ((allowedReasons & ALLOWED_METERED_REASON_USER_EXEMPTED) != 0) { + effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_DATA_SAVER; + } + } + } + private class NotificationId { private final String mTag; private final int mId; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index d5a9e3c0d4f8..e58836659189 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -10635,17 +10635,17 @@ public class NotificationManagerService extends SystemService { return true; } } - String toastMessage = "Indirect activity start from " + packageName; String logcatMessage = "Indirect notification activity start (trampoline) from " + packageName; - + // Call to toast() method is posted to mHandler below to offload PM lookup from the + // activity start path if (CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK, uid)) { - toast(toastMessage + " blocked."); + mHandler.post(() -> toast(packageName, uid, /* blocked */ true)); Slog.e(TAG, logcatMessage + " blocked"); return false; } else { if (mPackagesShown.add(packageName)) { - toast(toastMessage + ". This will be blocked in S."); + mHandler.post(() -> toast(packageName, uid, /* blocked */ false)); } Slog.w(TAG, logcatMessage + ", this should be avoided for performance reasons"); return true; @@ -10661,10 +10661,19 @@ public class NotificationManagerService extends SystemService { && !CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK, uid); } - private void toast(String message) { - mUiHandler.post(() -> - Toast.makeText(getUiContext(), message + "\nSee g.co/dev/trampolines.", - Toast.LENGTH_LONG).show()); + private void toast(String packageName, int uid, boolean blocked) { + final CharSequence label; + try { + label = mPackageManagerClient.getApplicationLabel( + mPackageManager.getApplicationInfo(packageName, 0, + UserHandle.getUserId(uid))); + } catch (RemoteException e) { + Slog.e(TAG, "Unexpected exception obtaining app label from PackageManager", e); + return; + } + mUiHandler.post(() -> Toast.makeText(getUiContext(), + label + " launch " + (blocked ? "blocked" : "will be blocked") + + "\ng.co/dev/trampolines", Toast.LENGTH_LONG).show()); } } } diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java index 4be509b3f464..1d556fec31ea 100644 --- a/services/core/java/com/android/server/pm/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/ComponentResolver.java @@ -1492,7 +1492,7 @@ public class ComponentResolver { } return null; } - final ResolveInfo res = new ResolveInfo(); + final ResolveInfo res = new ResolveInfo(info.hasCategory(Intent.CATEGORY_BROWSABLE)); res.activityInfo = ai; if ((mFlags & PackageManager.GET_RESOLVED_FILTER) != 0) { res.filter = info; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index dfe72b26f72a..1530e41917c7 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1768,6 +1768,11 @@ public class PackageManagerService extends IPackageManager.Stub public int[] getAllUserIds() { return mUserManager.getUserIds(); } + + @Override + public boolean doesUserExist(@UserIdInt int userId) { + return mUserManager.exists(userId); + } } /** @@ -2639,8 +2644,7 @@ public class PackageManagerService extends IPackageManager.Stub // We'll want to include browser possibilities in a few cases boolean includeBrowser = false; - if (!DomainVerificationUtils.isDomainVerificationIntent(intent, candidates, - matchFlags)) { + if (!DomainVerificationUtils.isDomainVerificationIntent(intent, matchFlags)) { result.addAll(undefinedList); // Maybe add one for the other profile. if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel @@ -25693,6 +25697,7 @@ public class PackageManagerService extends IPackageManager.Stub if (!convertedFromPreCreated || !readPermissionStateForUser(userId)) { mPermissionManager.onUserCreated(userId); mLegacyPermissionManager.grantDefaultPermissions(userId); + mDomainVerificationManager.clearUser(userId); } } diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java index 3b77c39cc31b..0c8e36b75425 100644 --- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java +++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java @@ -189,7 +189,7 @@ public class ArtStatsLogUtils { private static int getDexMetadataType(String dexMetadataPath) { if (dexMetadataPath == null) { - return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__NONE_DEX_METADATA; + return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_NONE; } StrictJarFile jarFile = null; try { @@ -199,17 +199,21 @@ public class ArtStatsLogUtils { boolean hasProfile = findFileName(jarFile, PROFILE_DEX_METADATA); boolean hasVdex = findFileName(jarFile, VDEX_DEX_METADATA); if (hasProfile && hasVdex) { - return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__PROFILE_AND_VDEX; + return ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE_AND_VDEX; } else if (hasProfile) { - return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__PROFILE; + return ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE; } else if (hasVdex) { - return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__VDEX; + return ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_VDEX; } else { - return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__UNKNOWN_DEX_METADATA; + return ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN; } } catch (IOException ignore) { Slog.e(TAG, "Error when parsing dex metadata " + dexMetadataPath); - return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__ERROR_DEX_METADATA; + return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_ERROR; } finally { try { if (jarFile != null) { diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 349561d3f1d1..37f317557aeb 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -986,7 +986,7 @@ public class DexManager { * Fetches the battery manager object and caches it if it hasn't been fetched already. */ private BatteryManager getBatteryManager() { - if (mBatteryManager == null) { + if (mBatteryManager == null && mContext != null) { mBatteryManager = mContext.getSystemService(BatteryManager.class); } @@ -1008,10 +1008,6 @@ public class DexManager { && mPowerManager.getCurrentThermalStatus() >= PowerManager.THERMAL_STATUS_SEVERE); - if (DEBUG) { - Log.d(TAG, "Battery, thermal, or memory are critical: " + isBtmCritical); - } - return isBtmCritical; } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java index b61fd8d633f6..39ed4882c69c 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; -import android.content.pm.verify.domain.DomainVerificationManager; import android.content.pm.verify.domain.DomainVerificationState; import android.os.UserHandle; import android.util.ArrayMap; @@ -151,7 +150,7 @@ public class DomainVerificationDebug { Integer state = reusedMap.valueAt(stateIndex); writer.print(domain); writer.print(": "); - writer.println(DomainVerificationManager.stateToDebugString(state)); + writer.println(DomainVerificationState.stateToDebugString(state)); } writer.decreaseIndent(); writer.decreaseIndent(); diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java index 1721a18f4f60..f4bcd3e65913 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java @@ -141,6 +141,12 @@ public class DomainVerificationEnforcer { "Caller is not allowed to edit other users"); } + if (!mCallback.doesUserExist(callingUserId)) { + throw new SecurityException("User " + callingUserId + " does not exist"); + } else if (!mCallback.doesUserExist(targetUserId)) { + throw new SecurityException("User " + targetUserId + " does not exist"); + } + return !mCallback.filterAppAccess(packageName, callingUid, targetUserId); } @@ -161,6 +167,12 @@ public class DomainVerificationEnforcer { Binder.getCallingPid(), callingUid, "Caller is not allowed to edit user selections"); + if (!mCallback.doesUserExist(callingUserId)) { + throw new SecurityException("User " + callingUserId + " does not exist"); + } else if (!mCallback.doesUserExist(targetUserId)) { + throw new SecurityException("User " + targetUserId + " does not exist"); + } + if (packageName == null) { return true; } @@ -184,6 +196,12 @@ public class DomainVerificationEnforcer { } } + if (!mCallback.doesUserExist(callingUserId)) { + throw new SecurityException("User " + callingUserId + " does not exist"); + } else if (!mCallback.doesUserExist(targetUserId)) { + throw new SecurityException("User " + targetUserId + " does not exist"); + } + return !mCallback.filterAppAccess(packageName, callingUid, targetUserId); } @@ -197,6 +215,12 @@ public class DomainVerificationEnforcer { "Caller is not allowed to edit other users"); } + if (!mCallback.doesUserExist(callingUserId)) { + throw new SecurityException("User " + callingUserId + " does not exist"); + } else if (!mCallback.doesUserExist(targetUserId)) { + throw new SecurityException("User " + targetUserId + " does not exist"); + } + return !mCallback.filterAppAccess(packageName, callingUid, targetUserId); } @@ -221,6 +245,12 @@ public class DomainVerificationEnforcer { mContext.enforcePermission( android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION, callingPid, callingUid, "Caller is not allowed to query user selections"); + + if (!mCallback.doesUserExist(callingUserId)) { + throw new SecurityException("User " + callingUserId + " does not exist"); + } else if (!mCallback.doesUserExist(targetUserId)) { + throw new SecurityException("User " + targetUserId + " does not exist"); + } } public interface Callback { @@ -229,5 +259,7 @@ public class DomainVerificationEnforcer { * if the package was not installed */ boolean filterAppAccess(@NonNull String packageName, int callingUid, @UserIdInt int userId); + + boolean doesUserExist(@UserIdInt int userId); } } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java index b4bcde726173..73182732cdaf 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java @@ -333,10 +333,10 @@ public interface DomainVerificationManagerInternal { @Nullable UUID getDomainVerificationInfoId(@NonNull String packageName); + @DomainVerificationManager.Error @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) - void setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId, - @NonNull Set<String> domains, int state) - throws IllegalArgumentException, NameNotFoundException; + int setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId, + @NonNull Set<String> domains, int state) throws NameNotFoundException; interface Connection extends DomainVerificationEnforcer.Callback { diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java index a7a52e0cd10c..2a17c6d4cec5 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java @@ -24,7 +24,6 @@ import android.content.pm.verify.domain.DomainOwner; import android.content.pm.verify.domain.DomainSet; import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationManager; -import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException; import android.content.pm.verify.domain.DomainVerificationUserState; import android.content.pm.verify.domain.IDomainVerificationManager; import android.os.ServiceSpecificException; @@ -61,11 +60,12 @@ public class DomainVerificationManagerStub extends IDomainVerificationManager.St } } + @DomainVerificationManager.Error @Override - public void setDomainVerificationStatus(String domainSetId, @NonNull DomainSet domainSet, + public int setDomainVerificationStatus(String domainSetId, @NonNull DomainSet domainSet, int state) { try { - mService.setDomainVerificationStatus(UUID.fromString(domainSetId), + return mService.setDomainVerificationStatus(UUID.fromString(domainSetId), domainSet.getDomains(), state); } catch (Exception e) { throw rethrow(e); @@ -82,11 +82,12 @@ public class DomainVerificationManagerStub extends IDomainVerificationManager.St } } + @DomainVerificationManager.Error @Override - public void setDomainVerificationUserSelection(String domainSetId, @NonNull DomainSet domainSet, + public int setDomainVerificationUserSelection(String domainSetId, @NonNull DomainSet domainSet, boolean enabled, @UserIdInt int userId) { try { - mService.setDomainVerificationUserSelection(UUID.fromString(domainSetId), + return mService.setDomainVerificationUserSelection(UUID.fromString(domainSetId), domainSet.getDomains(), enabled, userId); } catch (Exception e) { throw rethrow(e); @@ -116,14 +117,9 @@ public class DomainVerificationManagerStub extends IDomainVerificationManager.St } private RuntimeException rethrow(Exception exception) throws RuntimeException { - if (exception instanceof InvalidDomainSetException) { - int packedErrorCode = DomainVerificationManager.ERROR_INVALID_DOMAIN_SET; - packedErrorCode |= ((InvalidDomainSetException) exception).getReason() << 16; - return new ServiceSpecificException(packedErrorCode, - ((InvalidDomainSetException) exception).getPackageName()); - } else if (exception instanceof NameNotFoundException) { + if (exception instanceof NameNotFoundException) { return new ServiceSpecificException( - DomainVerificationManager.ERROR_NAME_NOT_FOUND); + DomainVerificationManager.INTERNAL_ERROR_NAME_NOT_FOUND); } else if (exception instanceof RuntimeException) { return (RuntimeException) exception; } else { diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index e85bbe41f747..a0e252a8a28a 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -19,6 +19,7 @@ package com.android.server.pm.verify.domain; import static java.util.Collections.emptyList; import static java.util.Collections.emptySet; +import android.annotation.CheckResult; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -34,7 +35,6 @@ import android.content.pm.parsing.component.ParsedActivity; import android.content.pm.verify.domain.DomainOwner; import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationManager; -import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException; import android.content.pm.verify.domain.DomainVerificationState; import android.content.pm.verify.domain.DomainVerificationUserState; import android.content.pm.verify.domain.IDomainVerificationManager; @@ -243,17 +243,17 @@ public class DomainVerificationService extends SystemService throws NameNotFoundException { mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy); synchronized (mLock) { - DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); - if (pkgState == null) { - return null; - } - AndroidPackage pkg = mConnection.getPackageLocked(packageName); if (pkg == null) { throw DomainVerificationUtils.throwPackageUnavailable(packageName); } - Map<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap()); + DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); + if (pkgState == null) { + return null; + } + + ArrayMap<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap()); // TODO(b/159952358): Should the domain list be cached? ArraySet<String> domains = mCollector.collectValidAutoVerifyDomains(pkg); @@ -267,44 +267,56 @@ public class DomainVerificationService extends SystemService DomainVerificationState.STATE_NO_RESPONSE); } + final int mapSize = hostToStateMap.size(); + for (int index = 0; index < mapSize; index++) { + int internalValue = hostToStateMap.valueAt(index); + int publicValue = DomainVerificationState.convertToInfoState(internalValue); + hostToStateMap.setValueAt(index, publicValue); + } + // TODO(b/159952358): Do not return if no values are editable (all ignored states)? return new DomainVerificationInfo(pkgState.getId(), packageName, hostToStateMap); } } - public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, - int state) throws InvalidDomainSetException, NameNotFoundException { + @DomainVerificationManager.Error + public int setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, + int state) throws NameNotFoundException { if (state < DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED) { if (state != DomainVerificationState.STATE_SUCCESS) { - throw new IllegalArgumentException( - "Verifier can only set STATE_SUCCESS or codes greater than or equal to " - + "STATE_FIRST_VERIFIER_DEFINED"); + return DomainVerificationManager.ERROR_INVALID_STATE_CODE; } } - setDomainVerificationStatusInternal(mConnection.getCallingUid(), domainSetId, domains, - state); + return setDomainVerificationStatusInternal(mConnection.getCallingUid(), domainSetId, + domains, state); } + @DomainVerificationManager.Error @Override - public void setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId, + public int setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId, @NonNull Set<String> domains, int state) - throws InvalidDomainSetException, NameNotFoundException { + throws NameNotFoundException { mEnforcer.assertApprovedVerifier(callingUid, mProxy); synchronized (mLock) { List<String> verifiedDomains = new ArrayList<>(); - DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains, + GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains, true /* forAutoVerify */, callingUid, null /* userId */); + if (result.isError()) { + return result.getErrorCode(); + } + + DomainVerificationPkgState pkgState = result.getPkgState(); ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); for (String domain : domains) { Integer previousState = stateMap.get(domain); if (previousState != null - && !DomainVerificationManager.isStateModifiable(previousState)) { + && !DomainVerificationState.isModifiable(previousState)) { continue; } - if (DomainVerificationManager.isStateVerified(state)) { + if (DomainVerificationState.isVerified(state)) { verifiedDomains.add(domain); } @@ -318,6 +330,7 @@ public class DomainVerificationService extends SystemService } mConnection.scheduleWriteSettings(); + return DomainVerificationManager.STATUS_OK; } @Override @@ -460,17 +473,24 @@ public class DomainVerificationService extends SystemService throw DomainVerificationUtils.throwPackageUnavailable(packageName); } - pkgState.getOrCreateUserState(userId) - .setLinkHandlingAllowed(allowed); + if (userId == UserHandle.USER_ALL) { + for (int aUserId : mConnection.getAllUserIds()) { + pkgState.getOrCreateUserState(aUserId) + .setLinkHandlingAllowed(allowed); + } + } else { + pkgState.getOrCreateUserState(userId) + .setLinkHandlingAllowed(allowed); + } } } mConnection.scheduleWriteSettings(); } - public void setDomainVerificationUserSelection(@NonNull UUID domainSetId, + public int setDomainVerificationUserSelection(@NonNull UUID domainSetId, @NonNull Set<String> domains, boolean enabled, @UserIdInt int userId) - throws InvalidDomainSetException, NameNotFoundException { + throws NameNotFoundException { synchronized (mLock) { final int callingUid = mConnection.getCallingUid(); // Pass null for package name here and do the app visibility enforcement inside @@ -478,14 +498,17 @@ public class DomainVerificationService extends SystemService // ID reason if the target app is invisible if (!mEnforcer.assertApprovedUserSelector(callingUid, mConnection.getCallingUserId(), null /* packageName */, userId)) { - throw new InvalidDomainSetException(domainSetId, null, - InvalidDomainSetException.REASON_ID_INVALID); + return DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID; } - DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains, + GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains, false /* forAutoVerify */, callingUid, userId); - DomainVerificationInternalUserState userState = - pkgState.getOrCreateUserState(userId); + if (result.isError()) { + return result.getErrorCode(); + } + + DomainVerificationPkgState pkgState = result.getPkgState(); + DomainVerificationInternalUserState userState = pkgState.getOrCreateUserState(userId); // Disable other packages if approving this one. Note that this check is only done for // enabling. This allows an escape hatch in case multiple packages somehow get selected. @@ -503,8 +526,7 @@ public class DomainVerificationService extends SystemService userId, APPROVAL_LEVEL_NONE + 1, mConnection::getPackageSettingLocked); int highestApproval = packagesToLevel.second; if (highestApproval > APPROVAL_LEVEL_SELECTION) { - throw new InvalidDomainSetException(domainSetId, null, - InvalidDomainSetException.REASON_UNABLE_TO_APPROVE); + return DomainVerificationManager.ERROR_UNABLE_TO_APPROVE; } domainToApprovedPackages.put(domain, packagesToLevel.first); @@ -544,6 +566,7 @@ public class DomainVerificationService extends SystemService } mConnection.scheduleWriteSettings(); + return DomainVerificationManager.STATUS_OK; } @Override @@ -636,16 +659,16 @@ public class DomainVerificationService extends SystemService throw DomainVerificationUtils.throwPackageUnavailable(packageName); } synchronized (mLock) { - DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); - if (pkgState == null) { - return null; - } - AndroidPackage pkg = mConnection.getPackageLocked(packageName); if (pkg == null) { throw DomainVerificationUtils.throwPackageUnavailable(packageName); } + DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); + if (pkgState == null) { + return null; + } + ArraySet<String> webDomains = mCollector.collectAllWebDomains(pkg); int webDomainsSize = webDomains.size(); @@ -659,7 +682,7 @@ public class DomainVerificationService extends SystemService Integer state = stateMap.get(host); int domainState; - if (state != null && DomainVerificationManager.isStateVerified(state)) { + if (state != null && DomainVerificationState.isVerified(state)) { domainState = DomainVerificationUserState.DOMAIN_STATE_VERIFIED; } else if (enabledHosts.contains(host)) { domainState = DomainVerificationUserState.DOMAIN_STATE_SELECTED; @@ -793,19 +816,12 @@ public class DomainVerificationService extends SystemService Integer oldStateInteger = oldStateMap.get(domain); if (oldStateInteger != null) { int oldState = oldStateInteger; - switch (oldState) { - case DomainVerificationState.STATE_SUCCESS: - case DomainVerificationState.STATE_RESTORED: - case DomainVerificationState.STATE_MIGRATED: - newStateMap.put(domain, oldState); - break; - default: - // In all other cases, the state code is left unset - // (STATE_NO_RESPONSE) to signal to the verification agent that any - // existing error has been cleared and the domain should be - // re-attempted. This makes update of a package a signal to - // re-verify. - break; + // If the following case fails, the state code is left unset + // (STATE_NO_RESPONSE) to signal to the verification agent that any existing + // error has been cleared and the domain should be re-attempted. This makes + // update of a package a signal to re-verify. + if (DomainVerificationState.shouldMigrate(oldState)) { + newStateMap.put(domain, oldState); } } } @@ -858,13 +874,13 @@ public class DomainVerificationService extends SystemService boolean sendBroadcast = true; DomainVerificationPkgState pkgState; - pkgState = mSettings.getPendingState(pkgName); + pkgState = mSettings.removePendingState(pkgName); if (pkgState != null) { // Don't send when attaching from pending read, which is usually boot scan. Re-send on // boot is handled in a separate method once all packages are added. sendBroadcast = false; } else { - pkgState = mSettings.getRestoredState(pkgName); + pkgState = mSettings.removeRestoredState(pkgName); } AndroidPackage pkg = newPkgSetting.getPkg(); @@ -872,7 +888,7 @@ public class DomainVerificationService extends SystemService boolean hasAutoVerifyDomains = !domains.isEmpty(); boolean isPendingOrRestored = pkgState != null; if (isPendingOrRestored) { - pkgState.setId(domainSetId); + pkgState = new DomainVerificationPkgState(pkgState, domainSetId, hasAutoVerifyDomains); } else { pkgState = new DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains); } @@ -1097,28 +1113,25 @@ public class DomainVerificationService extends SystemService * @param userIdForFilter which user to filter app access to, or null if the caller has already * validated package visibility */ + @CheckResult @GuardedBy("mLock") - private DomainVerificationPkgState getAndValidateAttachedLocked(@NonNull UUID domainSetId, + private GetAttachedResult getAndValidateAttachedLocked(@NonNull UUID domainSetId, @NonNull Set<String> domains, boolean forAutoVerify, int callingUid, - @Nullable Integer userIdForFilter) - throws InvalidDomainSetException, NameNotFoundException { + @Nullable Integer userIdForFilter) throws NameNotFoundException { if (domainSetId == null) { - throw new InvalidDomainSetException(null, null, - InvalidDomainSetException.REASON_ID_NULL); + return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL); } DomainVerificationPkgState pkgState = mAttachedPkgStates.get(domainSetId); if (pkgState == null) { - throw new InvalidDomainSetException(domainSetId, null, - InvalidDomainSetException.REASON_ID_INVALID); + return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID); } String pkgName = pkgState.getPackageName(); if (userIdForFilter != null && mConnection.filterAppAccess(pkgName, callingUid, userIdForFilter)) { - throw new InvalidDomainSetException(domainSetId, null, - InvalidDomainSetException.REASON_ID_INVALID); + return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID); } PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName); @@ -1127,8 +1140,8 @@ public class DomainVerificationService extends SystemService } if (CollectionUtils.isEmpty(domains)) { - throw new InvalidDomainSetException(domainSetId, pkgState.getPackageName(), - InvalidDomainSetException.REASON_SET_NULL_OR_EMPTY); + return GetAttachedResult.error( + DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY); } AndroidPackage pkg = pkgSetting.getPkg(); ArraySet<String> declaredDomains = forAutoVerify @@ -1136,11 +1149,10 @@ public class DomainVerificationService extends SystemService : mCollector.collectAllWebDomains(pkg); if (domains.retainAll(declaredDomains)) { - throw new InvalidDomainSetException(domainSetId, pkgState.getPackageName(), - InvalidDomainSetException.REASON_UNKNOWN_DOMAIN); + return GetAttachedResult.error(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN); } - return pkgState; + return GetAttachedResult.success(pkgState); } @Override @@ -1185,7 +1197,7 @@ public class DomainVerificationService extends SystemService /** * Determine whether or not a broadcast should be sent at boot for the given {@param pkgState}. * Sends only if the only states recorded are default as decided by {@link - * DomainVerificationManager#isStateDefault(int)}. + * DomainVerificationState#isDefault(int)}. * * If any other state is set, it's assumed that the domain verification agent is aware of the * package and has already scheduled future verification requests. @@ -1199,7 +1211,7 @@ public class DomainVerificationService extends SystemService int statesSize = stateMap.size(); for (int stateIndex = 0; stateIndex < statesSize; stateIndex++) { Integer state = stateMap.valueAt(stateIndex); - if (!DomainVerificationManager.isStateDefault(state)) { + if (!DomainVerificationState.isDefault(state)) { return false; } } @@ -1318,83 +1330,50 @@ public class DomainVerificationService extends SystemService @NonNull Function<String, PackageSetting> pkgSettingFunction) { String domain = intent.getData().getHost(); - // Collect package names - ArrayMap<String, Integer> packageApprovals = new ArrayMap<>(); + // Collect valid infos + ArrayMap<ResolveInfo, Integer> infoApprovals = new ArrayMap<>(); int infosSize = infos.size(); for (int index = 0; index < infosSize; index++) { - packageApprovals.put(infos.get(index).getComponentInfo().packageName, - APPROVAL_LEVEL_NONE); + final ResolveInfo info = infos.get(index); + // Only collect for intent filters that can auto resolve + if (info.isAutoResolutionAllowed()) { + infoApprovals.put(info, null); + } } // Find all approval levels - int highestApproval = fillMapWithApprovalLevels(packageApprovals, domain, userId, + int highestApproval = fillMapWithApprovalLevels(infoApprovals, domain, userId, pkgSettingFunction); if (highestApproval == APPROVAL_LEVEL_NONE) { return Pair.create(emptyList(), highestApproval); } - // Filter to highest, non-zero packages - ArraySet<String> approvedPackages = new ArraySet<>(); - int approvalsSize = packageApprovals.size(); - for (int index = 0; index < approvalsSize; index++) { - if (packageApprovals.valueAt(index) == highestApproval) { - approvedPackages.add(packageApprovals.keyAt(index)); + // Filter to highest, non-zero infos + for (int index = infoApprovals.size() - 1; index >= 0; index--) { + if (infoApprovals.valueAt(index) != highestApproval) { + infoApprovals.removeAt(index); } } - ArraySet<String> filteredPackages = new ArraySet<>(); - if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) { + if (highestApproval != APPROVAL_LEVEL_LEGACY_ASK) { // To maintain legacy behavior while the Settings API is not implemented, // show the chooser if all approved apps are marked ask, skipping the // last app, last declaration filtering. - filteredPackages.addAll(approvedPackages); - } else { - // Filter to last installed package - long latestInstall = Long.MIN_VALUE; - int approvedSize = approvedPackages.size(); - for (int index = 0; index < approvedSize; index++) { - String packageName = approvedPackages.valueAt(index); - PackageSetting pkgSetting = pkgSettingFunction.apply(packageName); - if (pkgSetting == null) { - continue; - } - long installTime = pkgSetting.getFirstInstallTime(); - if (installTime > latestInstall) { - latestInstall = installTime; - filteredPackages.clear(); - filteredPackages.add(packageName); - } else if (installTime == latestInstall) { - filteredPackages.add(packageName); - } - } + filterToLastFirstInstalled(infoApprovals, pkgSettingFunction); } - // Filter to approved ResolveInfos - ArrayMap<String, List<ResolveInfo>> approvedInfos = new ArrayMap<>(); - for (int index = 0; index < infosSize; index++) { - ResolveInfo info = infos.get(index); - String packageName = info.getComponentInfo().packageName; - if (filteredPackages.contains(packageName)) { - List<ResolveInfo> infosPerPackage = approvedInfos.get(packageName); - if (infosPerPackage == null) { - infosPerPackage = new ArrayList<>(); - approvedInfos.put(packageName, infosPerPackage); - } - infosPerPackage.add(info); - } + // Easier to transform into list as the filterToLastDeclared method + // requires swapping indexes, which doesn't work with ArrayMap keys + final int size = infoApprovals.size(); + List<ResolveInfo> finalList = new ArrayList<>(size); + for (int index = 0; index < size; index++) { + finalList.add(infoApprovals.keyAt(index)); } - List<ResolveInfo> finalList; - if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) { - // If legacy ask, skip the last declaration filtering - finalList = new ArrayList<>(); - int size = approvedInfos.size(); - for (int index = 0; index < size; index++) { - finalList.addAll(approvedInfos.valueAt(index)); - } - } else { + // If legacy ask, skip the last declaration filtering + if (highestApproval != APPROVAL_LEVEL_LEGACY_ASK) { // Find the last declared ResolveInfo per package - finalList = filterToLastDeclared(approvedInfos, pkgSettingFunction); + filterToLastDeclared(finalList, pkgSettingFunction); } return Pair.create(finalList, highestApproval); @@ -1403,68 +1382,127 @@ public class DomainVerificationService extends SystemService /** * @return highest approval level found */ - private int fillMapWithApprovalLevels(@NonNull ArrayMap<String, Integer> inputMap, + @ApprovalLevel + private int fillMapWithApprovalLevels(@NonNull ArrayMap<ResolveInfo, Integer> inputMap, @NonNull String domain, @UserIdInt int userId, @NonNull Function<String, PackageSetting> pkgSettingFunction) { int highestApproval = APPROVAL_LEVEL_NONE; int size = inputMap.size(); for (int index = 0; index < size; index++) { - String packageName = inputMap.keyAt(index); + if (inputMap.valueAt(index) != null) { + // Already filled by previous iteration + continue; + } + + ResolveInfo info = inputMap.keyAt(index); + final String packageName = info.getComponentInfo().packageName; PackageSetting pkgSetting = pkgSettingFunction.apply(packageName); if (pkgSetting == null) { - inputMap.setValueAt(index, APPROVAL_LEVEL_NONE); + fillInfoMapForSamePackage(inputMap, packageName, APPROVAL_LEVEL_NONE); continue; } int approval = approvalLevelForDomain(pkgSetting, domain, userId, domain); highestApproval = Math.max(highestApproval, approval); - inputMap.setValueAt(index, approval); + fillInfoMapForSamePackage(inputMap, packageName, approval); } return highestApproval; } + private void fillInfoMapForSamePackage(@NonNull ArrayMap<ResolveInfo, Integer> inputMap, + @NonNull String targetPackageName, @ApprovalLevel int level) { + final int size = inputMap.size(); + for (int index = 0; index < size; index++) { + final String packageName = inputMap.keyAt(index).getComponentInfo().packageName; + if (Objects.equals(targetPackageName, packageName)) { + inputMap.setValueAt(index, level); + } + } + } + @NonNull - private List<ResolveInfo> filterToLastDeclared( - @NonNull ArrayMap<String, List<ResolveInfo>> inputMap, + private void filterToLastFirstInstalled(@NonNull ArrayMap<ResolveInfo, Integer> inputMap, @NonNull Function<String, PackageSetting> pkgSettingFunction) { - List<ResolveInfo> finalList = new ArrayList<>(inputMap.size()); - - int inputSize = inputMap.size(); - for (int inputIndex = 0; inputIndex < inputSize; inputIndex++) { - String packageName = inputMap.keyAt(inputIndex); - List<ResolveInfo> infos = inputMap.valueAt(inputIndex); + // First, find the package with the latest first install time + String targetPackageName = null; + long latestInstall = Long.MIN_VALUE; + final int size = inputMap.size(); + for (int index = 0; index < size; index++) { + ResolveInfo info = inputMap.keyAt(index); + String packageName = info.getComponentInfo().packageName; PackageSetting pkgSetting = pkgSettingFunction.apply(packageName); + if (pkgSetting == null) { + continue; + } + + long installTime = pkgSetting.getFirstInstallTime(); + if (installTime > latestInstall) { + latestInstall = installTime; + targetPackageName = packageName; + } + } + + // Then, remove all infos that don't match the package + for (int index = inputMap.size() - 1; index >= 0; index--) { + ResolveInfo info = inputMap.keyAt(index); + if (!Objects.equals(targetPackageName, info.getComponentInfo().packageName)) { + inputMap.removeAt(index); + } + } + } + + @NonNull + private void filterToLastDeclared(@NonNull List<ResolveInfo> inputList, + @NonNull Function<String, PackageSetting> pkgSettingFunction) { + // Must call size each time as the size of the list will decrease + for (int index = 0; index < inputList.size(); index++) { + ResolveInfo info = inputList.get(index); + String targetPackageName = info.getComponentInfo().packageName; + PackageSetting pkgSetting = pkgSettingFunction.apply(targetPackageName); AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg(); if (pkg == null) { continue; } - ResolveInfo result = null; - int highestIndex = -1; - int infosSize = infos.size(); - for (int infoIndex = 0; infoIndex < infosSize; infoIndex++) { - ResolveInfo info = infos.get(infoIndex); - List<ParsedActivity> activities = pkg.getActivities(); - int activitiesSize = activities.size(); - for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) { - if (Objects.equals(activities.get(activityIndex).getComponentName(), - info.getComponentInfo().getComponentName())) { - if (activityIndex > highestIndex) { - highestIndex = activityIndex; - result = info; - } - break; - } + ResolveInfo result = info; + int highestIndex = indexOfIntentFilterEntry(pkg, result); + + // Search backwards so that lower results can be removed as they're found + for (int searchIndex = inputList.size() - 1; searchIndex >= index + 1; searchIndex--) { + ResolveInfo searchInfo = inputList.get(searchIndex); + if (!Objects.equals(targetPackageName, searchInfo.getComponentInfo().packageName)) { + continue; } + + int entryIndex = indexOfIntentFilterEntry(pkg, searchInfo); + if (entryIndex > highestIndex) { + highestIndex = entryIndex; + result = searchInfo; + } + + // Always remove the entry so that the current index + // is left as the sole candidate of the target package + inputList.remove(searchIndex); } - // Shouldn't be null, but might as well be safe - if (result != null) { - finalList.add(result); + // Swap the current index for the result, leaving this as + // the only entry with the target package name + inputList.set(index, result); + } + } + + private int indexOfIntentFilterEntry(@NonNull AndroidPackage pkg, + @NonNull ResolveInfo target) { + List<ParsedActivity> activities = pkg.getActivities(); + int activitiesSize = activities.size(); + for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) { + if (Objects.equals(activities.get(activityIndex).getComponentName(), + target.getComponentInfo().getComponentName())) { + return activityIndex; } } - return finalList; + return -1; } @Override @@ -1472,8 +1510,7 @@ public class DomainVerificationService extends SystemService @NonNull List<ResolveInfo> candidates, @PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) { String packageName = pkgSetting.getName(); - if (!DomainVerificationUtils.isDomainVerificationIntent(intent, candidates, - resolveInfoFlags)) { + if (!DomainVerificationUtils.isDomainVerificationIntent(intent, resolveInfoFlags)) { if (DEBUG_APPROVAL) { debugApproval(packageName, intent, userId, false, "not valid intent"); } @@ -1542,7 +1579,7 @@ public class DomainVerificationService extends SystemService ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); // Check if the exact host matches Integer state = stateMap.get(host); - if (state != null && DomainVerificationManager.isStateVerified(state)) { + if (state != null && DomainVerificationState.isVerified(state)) { if (DEBUG_APPROVAL) { debugApproval(packageName, debugObject, userId, true, "host verified exactly"); @@ -1553,7 +1590,7 @@ public class DomainVerificationService extends SystemService // Otherwise see if the host matches a verified domain by wildcard int stateMapSize = stateMap.size(); for (int index = 0; index < stateMapSize; index++) { - if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) { + if (!DomainVerificationState.isVerified(stateMap.valueAt(index))) { continue; } @@ -1672,4 +1709,40 @@ public class DomainVerificationService extends SystemService Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for " + debugObject + " for user " + userId + ": " + reason); } + + private static class GetAttachedResult { + + @Nullable + private DomainVerificationPkgState mPkgState; + + private int mErrorCode; + + GetAttachedResult(@Nullable DomainVerificationPkgState pkgState, int errorCode) { + mPkgState = pkgState; + mErrorCode = errorCode; + } + + @NonNull + static GetAttachedResult error(@DomainVerificationManager.Error int errorCode) { + return new GetAttachedResult(null, errorCode); + } + + @NonNull + static GetAttachedResult success(@NonNull DomainVerificationPkgState pkgState) { + return new GetAttachedResult(pkgState, DomainVerificationManager.STATUS_OK); + } + + @NonNull + DomainVerificationPkgState getPkgState() { + return mPkgState; + } + + boolean isError() { + return mErrorCode != DomainVerificationManager.STATUS_OK; + } + + public int getErrorCode() { + return mErrorCode; + } + } } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java index f3d1dbb1f6ad..8b59da7bb944 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java @@ -256,16 +256,16 @@ class DomainVerificationSettings { } @Nullable - public DomainVerificationPkgState getPendingState(@NonNull String pkgName) { + public DomainVerificationPkgState removePendingState(@NonNull String pkgName) { synchronized (mLock) { - return mPendingPkgStates.get(pkgName); + return mPendingPkgStates.remove(pkgName); } } @Nullable - public DomainVerificationPkgState getRestoredState(@NonNull String pkgName) { + public DomainVerificationPkgState removeRestoredState(@NonNull String pkgName) { synchronized (mLock) { - return mRestoredPkgStates.get(pkgName); + return mRestoredPkgStates.remove(pkgName); } } } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java index 94767f555574..7e755fa384db 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java @@ -38,6 +38,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; +import java.util.function.Function; public class DomainVerificationShell { @@ -226,22 +228,19 @@ public class DomainVerificationShell { userId = translateUserId(userId, "runSetAppLinksUserState"); - String enabledString = commandHandler.getNextArgRequired(); + String enabledArg = commandHandler.getNextArg(); + if (TextUtils.isEmpty(enabledArg)) { + commandHandler.getErrPrintWriter().println("Error: enabled param not specified"); + return false; + } - // Manually ensure that "true" and "false" are the only options, to ensure a domain isn't - // accidentally parsed as a boolean boolean enabled; - switch (enabledString) { - case "true": - enabled = true; - break; - case "false": - enabled = false; - break; - default: - commandHandler.getErrPrintWriter().println( - "Invalid enabled param: " + enabledString); - return false; + try { + enabled = parseEnabled(enabledArg); + } catch (IllegalArgumentException e) { + commandHandler.getErrPrintWriter() + .println("Error: invalid enabled param: " + e.getMessage()); + return false; } ArraySet<String> domains = new ArraySet<>(getRemainingArgs(commandHandler)); @@ -255,8 +254,8 @@ public class DomainVerificationShell { } try { - mCallback.setDomainVerificationUserSelectionInternal(userId, - packageName, enabled, domains); + mCallback.setDomainVerificationUserSelectionInternal(userId, packageName, enabled, + domains); } catch (NameNotFoundException e) { commandHandler.getErrPrintWriter().println("Package not found: " + packageName); return false; @@ -362,15 +361,12 @@ public class DomainVerificationShell { private boolean runSetAppLinksAllowed(@NonNull BasicShellCommandHandler commandHandler) { String packageName = null; Integer userId = null; - Boolean allowed = null; String option; while ((option = commandHandler.getNextOption()) != null) { if (option.equals("--package")) { - packageName = commandHandler.getNextArgRequired(); - } if (option.equals("--user")) { + packageName = commandHandler.getNextArg(); + } else if (option.equals("--user")) { userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired()); - } else if (allowed == null) { - allowed = Boolean.valueOf(option); } else { commandHandler.getErrPrintWriter().println("Error: unexpected option: " + option); return false; @@ -389,11 +385,21 @@ public class DomainVerificationShell { return false; } - if (allowed == null) { + String allowedArg = commandHandler.getNextArg(); + if (TextUtils.isEmpty(allowedArg)) { commandHandler.getErrPrintWriter().println("Error: allowed setting not specified"); return false; } + boolean allowed; + try { + allowed = parseEnabled(allowedArg); + } catch (IllegalArgumentException e) { + commandHandler.getErrPrintWriter() + .println("Error: invalid allowed setting: " + e.getMessage()); + return false; + } + userId = translateUserId(userId, "runSetAppLinksAllowed"); try { @@ -422,6 +428,22 @@ public class DomainVerificationShell { } /** + * Manually ensure that "true" and "false" are the only options, to ensure a domain isn't + * accidentally parsed as a boolean. + */ + @NonNull + private boolean parseEnabled(@NonNull String arg) throws IllegalArgumentException { + switch (arg.toLowerCase(Locale.US)) { + case "true": + return true; + case "false": + return false; + default: + throw new IllegalArgumentException(arg + " is not a valid boolean"); + } + } + + /** * Separated interface from {@link DomainVerificationManagerInternal} to hide methods that are * even more internal, and so that testing is easier. */ @@ -498,7 +520,8 @@ public class DomainVerificationShell { void verifyPackages(@Nullable List<String> packageNames, boolean reVerify); /** - * @see DomainVerificationManagerInternal#printState(IndentingPrintWriter, String, Integer) + * @see DomainVerificationManagerInternal#printState(IndentingPrintWriter, String, Integer, + * Function) */ void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName, @Nullable @UserIdInt Integer userId) throws NameNotFoundException; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java index 883bbad1bd2d..cb3b5c9db7e7 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java @@ -22,7 +22,6 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; import android.os.Binder; import com.android.internal.util.CollectionUtils; @@ -30,7 +29,6 @@ import com.android.server.compat.PlatformCompat; import com.android.server.pm.PackageManagerService; import com.android.server.pm.parsing.pkg.AndroidPackage; -import java.util.List; import java.util.Set; public final class DomainVerificationUtils { @@ -46,7 +44,6 @@ public final class DomainVerificationUtils { } public static boolean isDomainVerificationIntent(Intent intent, - @NonNull List<ResolveInfo> candidates, @PackageManager.ResolveInfoFlags int resolveInfoFlags) { if (!intent.isWebIntent()) { return false; @@ -63,42 +60,18 @@ public final class DomainVerificationUtils { && intent.hasCategory(Intent.CATEGORY_BROWSABLE); } - // In cases where at least one browser is resolved and only one non-browser is resolved, - // the Intent is coerced into an app links intent, under the assumption the browser can - // be skipped if the app is approved at any level for the domain. - boolean foundBrowser = false; - boolean foundOneApp = false; - - final int candidatesSize = candidates.size(); - for (int index = 0; index < candidatesSize; index++) { - final ResolveInfo info = candidates.get(index); - if (info.handleAllWebDataURI) { - foundBrowser = true; - } else if (foundOneApp) { - // Already true, so duplicate app - foundOneApp = false; - break; - } else { - foundOneApp = true; - } - } - boolean matchDefaultByFlags = (resolveInfoFlags & PackageManager.MATCH_DEFAULT_ONLY) != 0; - boolean onlyOneNonBrowser = foundBrowser && foundOneApp; // Check if matches (BROWSABLE || none) && DEFAULT if (categoriesSize == 0) { - // No categories, run coerce case, matching DEFAULT by flags - return onlyOneNonBrowser && matchDefaultByFlags; - } else if (intent.hasCategory(Intent.CATEGORY_DEFAULT)) { - // Run coerce case, matching by explicit DEFAULT - return onlyOneNonBrowser; + // No categories, only allow matching DEFAULT by flags + return matchDefaultByFlags; } else if (intent.hasCategory(Intent.CATEGORY_BROWSABLE)) { // Intent matches BROWSABLE, must match DEFAULT by flags return matchDefaultByFlags; } else { - // Otherwise not matching any app link categories - return false; + // Otherwise only needs to have DEFAULT + return intent.hasCategory(Intent.CATEGORY_DEFAULT); } } diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java index a089a6022735..40c70915f3ce 100644 --- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java +++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java @@ -68,6 +68,12 @@ public class DomainVerificationPkgState { this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0)); } + public DomainVerificationPkgState(@NonNull DomainVerificationPkgState pkgState, + @NonNull UUID id, boolean hasAutoVerifyDomains) { + this(pkgState.getPackageName(), id, hasAutoVerifyDomains, pkgState.getStateMap(), + pkgState.getUserStates()); + } + @Nullable public DomainVerificationInternalUserState getUserState(@UserIdInt int userId) { return mUserStates.get(userId); @@ -84,10 +90,6 @@ public class DomainVerificationPkgState { return userState; } - public void setId(@NonNull UUID id) { - mId = id; - } - public void removeUser(@UserIdInt int userId) { mUserStates.remove(userId); } diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java index 18042af139a3..fa36683e4aff 100644 --- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java +++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java @@ -207,20 +207,24 @@ public class DomainVerificationProxyV1 implements DomainVerificationProxy { int callingUid = response.callingUid; if (!successfulDomains.isEmpty()) { try { - mManager.setDomainVerificationStatusInternal(callingUid, domainSetId, - successfulDomains, DomainVerificationState.STATE_SUCCESS); - } catch (DomainVerificationManager.InvalidDomainSetException - | PackageManager.NameNotFoundException e) { + if (mManager.setDomainVerificationStatusInternal(callingUid, domainSetId, + successfulDomains, DomainVerificationState.STATE_SUCCESS) + != DomainVerificationManager.STATUS_OK) { + Slog.e(TAG, "Failure reporting successful domains for " + packageName); + } + } catch (Exception e) { Slog.e(TAG, "Failure reporting successful domains for " + packageName, e); } } if (!failedDomains.isEmpty()) { try { - mManager.setDomainVerificationStatusInternal(callingUid, domainSetId, - failedDomains, DomainVerificationState.STATE_LEGACY_FAILURE); - } catch (DomainVerificationManager.InvalidDomainSetException - | PackageManager.NameNotFoundException e) { + if (mManager.setDomainVerificationStatusInternal(callingUid, domainSetId, + failedDomains, DomainVerificationState.STATE_LEGACY_FAILURE) + != DomainVerificationManager.STATUS_OK) { + Slog.e(TAG, "Failure reporting failed domains for " + packageName); + } + } catch (Exception e) { Slog.e(TAG, "Failure reporting failed domains for " + packageName, e); } } diff --git a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java new file mode 100644 index 000000000000..f00f856c2910 --- /dev/null +++ b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2021 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.timedetector; + +import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED; +import static android.app.time.Capabilities.CAPABILITY_POSSESSED; + +import android.annotation.UserIdInt; +import android.app.time.Capabilities.CapabilityState; +import android.app.time.TimeCapabilities; +import android.app.time.TimeCapabilitiesAndConfig; +import android.app.time.TimeConfiguration; +import android.os.UserHandle; + +import java.util.Objects; + +/** + * Holds configuration values that affect time behaviour. + */ +public final class ConfigurationInternal { + + private final @UserIdInt int mUserId; + private final boolean mUserConfigAllowed; + private final boolean mAutoDetectionEnabled; + + private ConfigurationInternal(Builder builder) { + mUserId = builder.mUserId; + mUserConfigAllowed = builder.mUserConfigAllowed; + mAutoDetectionEnabled = builder.mAutoDetectionEnabled; + } + + /** Returns a {@link TimeCapabilitiesAndConfig} objects based on configuration values. */ + public TimeCapabilitiesAndConfig capabilitiesAndConfig() { + return new TimeCapabilitiesAndConfig(timeCapabilities(), timeConfiguration()); + } + + private TimeConfiguration timeConfiguration() { + return new TimeConfiguration.Builder() + .setAutoDetectionEnabled(mAutoDetectionEnabled) + .build(); + } + + private TimeCapabilities timeCapabilities() { + @CapabilityState int configureAutoTimeDetectionEnabledCapability = + mUserConfigAllowed + ? CAPABILITY_POSSESSED + : CAPABILITY_NOT_ALLOWED; + + @CapabilityState int suggestTimeManuallyCapability = + mUserConfigAllowed + ? CAPABILITY_POSSESSED + : CAPABILITY_NOT_ALLOWED; + + return new TimeCapabilities.Builder(UserHandle.of(mUserId)) + .setConfigureAutoTimeDetectionEnabledCapability( + configureAutoTimeDetectionEnabledCapability) + .setSuggestTimeManuallyCapability(suggestTimeManuallyCapability) + .build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ConfigurationInternal that = (ConfigurationInternal) o; + return mUserId == that.mUserId + && mUserConfigAllowed == that.mUserConfigAllowed + && mAutoDetectionEnabled == that.mAutoDetectionEnabled; + } + + @Override + public int hashCode() { + return Objects.hash(mUserId, mUserConfigAllowed, mAutoDetectionEnabled); + } + + @Override + public String toString() { + return "ConfigurationInternal{" + + "mUserId=" + mUserId + + ", mUserConfigAllowed=" + mUserConfigAllowed + + ", mAutoDetectionEnabled=" + mAutoDetectionEnabled + + '}'; + } + + static final class Builder { + private final @UserIdInt int mUserId; + private boolean mUserConfigAllowed; + private boolean mAutoDetectionEnabled; + + Builder(@UserIdInt int userId) { + mUserId = userId; + } + + Builder setUserConfigAllowed(boolean userConfigAllowed) { + mUserConfigAllowed = userConfigAllowed; + return this; + } + + Builder setAutoDetectionEnabled(boolean autoDetectionEnabled) { + mAutoDetectionEnabled = autoDetectionEnabled; + return this; + } + + ConfigurationInternal build() { + return new ConfigurationInternal(this); + } + } + +} diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java index 5cd171839996..4f5e8fa9c944 100644 --- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java +++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java @@ -21,6 +21,7 @@ import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_TELEPH import static com.android.server.timedetector.TimeDetectorStrategy.stringToOrigin; import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.app.AlarmManager; import android.content.ContentResolver; import android.content.Context; @@ -28,6 +29,8 @@ import android.os.Build; import android.os.PowerManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.util.Slog; @@ -71,6 +74,7 @@ public final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environme @NonNull private final ContentResolver mContentResolver; @NonNull private final PowerManager.WakeLock mWakeLock; @NonNull private final AlarmManager mAlarmManager; + @NonNull private final UserManager mUserManager; @NonNull private final int[] mOriginPriorities; public EnvironmentImpl(@NonNull Context context) { @@ -83,6 +87,8 @@ public final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environme mAlarmManager = Objects.requireNonNull(context.getSystemService(AlarmManager.class)); + mUserManager = Objects.requireNonNull(context.getSystemService(UserManager.class)); + mSystemClockUpdateThresholdMillis = SystemProperties.getInt("ro.sys.time_detector_update_diff", SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT); @@ -115,6 +121,14 @@ public final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environme } @Override + public ConfigurationInternal configurationInternal(@UserIdInt int userId) { + return new ConfigurationInternal.Builder(userId) + .setUserConfigAllowed(isUserConfigAllowed(userId)) + .setAutoDetectionEnabled(isAutoTimeDetectionEnabled()) + .build(); + } + + @Override public void acquireWakeLock() { if (mWakeLock.isHeld()) { Slog.wtf(TAG, "WakeLock " + mWakeLock + " already held"); @@ -150,6 +164,11 @@ public final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environme } } + private boolean isUserConfigAllowed(@UserIdInt int userId) { + UserHandle userHandle = UserHandle.of(userId); + return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle); + } + private static int[] getOriginPriorities(@NonNull Context context) { String[] originStrings = context.getResources().getStringArray(R.array.config_autoTimeSourcesPriority); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index b210339adf79..eefa045abe1b 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -18,7 +18,10 @@ package com.android.server.timedetector; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; +import android.app.time.TimeCapabilitiesAndConfig; +import android.app.time.TimeConfiguration; import android.app.timedetector.GnssTimeSuggestion; import android.app.timedetector.ITimeDetectorService; import android.app.timedetector.ManualTimeSuggestion; @@ -36,6 +39,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.SystemService; +import com.android.server.timezonedetector.CallerIdentityInjector; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -71,6 +75,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { @NonNull private final Handler mHandler; @NonNull private final Context mContext; @NonNull private final TimeDetectorStrategy mTimeDetectorStrategy; + @NonNull private final CallerIdentityInjector mCallerIdentityInjector; private static TimeDetectorService create(@NonNull Context context) { TimeDetectorStrategyImpl.Environment environment = new EnvironmentImpl(context); @@ -97,9 +102,42 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { @VisibleForTesting public TimeDetectorService(@NonNull Context context, @NonNull Handler handler, @NonNull TimeDetectorStrategy timeDetectorStrategy) { + this(context, handler, timeDetectorStrategy, CallerIdentityInjector.REAL); + } + + @VisibleForTesting + public TimeDetectorService(@NonNull Context context, @NonNull Handler handler, + @NonNull TimeDetectorStrategy timeDetectorStrategy, + @NonNull CallerIdentityInjector callerIdentityInjector) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy); + mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector); + } + + @Override + public TimeCapabilitiesAndConfig getCapabilitiesAndConfig() { + int userId = mCallerIdentityInjector.getCallingUserId(); + return getTimeCapabilitiesAndConfig(userId); + } + + private TimeCapabilitiesAndConfig getTimeCapabilitiesAndConfig(@UserIdInt int userId) { + enforceManageTimeDetectorPermission(); + + final long token = mCallerIdentityInjector.clearCallingIdentity(); + try { + ConfigurationInternal configurationInternal = + mTimeDetectorStrategy.getConfigurationInternal(userId); + return configurationInternal.capabilitiesAndConfig(); + } finally { + mCallerIdentityInjector.restoreCallingIdentity(token); + } + } + + @Override + public boolean updateConfiguration(TimeConfiguration timeConfiguration) { + // TODO(b/172891783) Add actual logic + return false; } @Override @@ -193,4 +231,11 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { android.Manifest.permission.SET_TIME, "suggest time from external source"); } + + private void enforceManageTimeDetectorPermission() { + mContext.enforceCallingPermission( + android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION, + "manage time and time zone detection"); + } + } diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java index 792f372b0c49..cde66becdee2 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java @@ -18,6 +18,7 @@ package com.android.server.timedetector; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; import android.app.timedetector.GnssTimeSuggestion; import android.app.timedetector.ManualTimeSuggestion; @@ -88,6 +89,9 @@ public interface TimeDetectorStrategy extends Dumpable { /** Processes the suggested time from external sources. */ void suggestExternalTime(@NonNull ExternalTimeSuggestion timeSuggestion); + /** Returns the configuration that controls time detector behaviour for specified user. */ + ConfigurationInternal getConfigurationInternal(@UserIdInt int userId); + /** * Handles the auto-time configuration changing For example, when the auto-time setting is * toggled on or off. diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index 16e8632c6e40..289d8d6e648e 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -22,6 +22,7 @@ import static java.util.stream.Collectors.joining; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.AlarmManager; import android.app.time.ExternalTimeSuggestion; import android.app.timedetector.GnssTimeSuggestion; @@ -155,6 +156,12 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { */ @Origin int[] autoOriginPriorities(); + /** + * Returns {@link ConfigurationInternal} for specified user. + */ + @NonNull + ConfigurationInternal configurationInternal(@UserIdInt int userId); + /** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */ void acquireWakeLock(); @@ -267,6 +274,12 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } @Override + @NonNull + public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) { + return mEnvironment.configurationInternal(userId); + } + + @Override public synchronized void handleAutoTimeConfigChanged() { boolean enabled = mEnvironment.isAutoTimeDetectionEnabled(); // When automatic time detection is enabled we update the system clock instantly if we can. diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java index ee78a4e4d1cf..b4aa20130791 100644 --- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java +++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java @@ -16,10 +16,10 @@ package com.android.server.timezonedetector; -import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED; -import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE; -import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED; -import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED; +import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED; +import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE; +import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED; +import static android.app.time.Capabilities.CAPABILITY_POSSESSED; import android.annotation.NonNull; import android.annotation.UserIdInt; diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index 5d34dd7daffb..c34a7d37ba24 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -15,7 +15,7 @@ */ package com.android.server.timezonedetector; -import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED; +import static android.app.time.Capabilities.CAPABILITY_POSSESSED; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index a909c6d119e8..b12ce67832ef 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1479,7 +1479,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private void setCornersRadius(WindowState mainWindow, int cornersRadius) { final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface(); if (windowSurface != null && windowSurface.isValid()) { - Transaction transaction = getPendingTransaction(); + Transaction transaction = getSyncTransaction(); transaction.setCornerRadius(windowSurface, cornersRadius); } } @@ -1491,7 +1491,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } layoutLetterbox(winHint); if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) { - mLetterbox.applySurfaceChanges(getPendingTransaction()); + mLetterbox.applySurfaceChanges(getSyncTransaction()); } } @@ -2247,6 +2247,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Go ahead and cancel the request. ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Clearing startingData for token=%s", this); mStartingData = null; + // Clean surface up since we don't want the window to be added back, so we don't + // need to keep the surface to remove it. + mStartingSurface = null; } return; } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index 5ccf576e1099..9855ea50c83d 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -451,7 +451,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { void sendDisplayAreaVanished(IDisplayAreaOrganizer organizer) { if (organizer == null) return; - migrateToNewSurfaceControl(); + migrateToNewSurfaceControl(getSyncTransaction()); mOrganizerController.onDisplayAreaVanished(organizer, this); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index c78f9ec21516..95b2b5d088a0 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3428,7 +3428,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Updates the layer assignment of windows on this display. */ void assignWindowLayers(boolean setLayoutNeeded) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "assignWindowLayers"); - assignChildLayers(getPendingTransaction()); + assignChildLayers(getSyncTransaction()); if (setLayoutNeeded) { setLayoutNeeded(); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index ad0ce5bf4b28..09a8e4f23644 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2382,11 +2382,11 @@ class Task extends WindowContainer<WindowContainer> { } @Override - void migrateToNewSurfaceControl() { - super.migrateToNewSurfaceControl(); + void migrateToNewSurfaceControl(SurfaceControl.Transaction t) { + super.migrateToNewSurfaceControl(t); mLastSurfaceSize.x = 0; mLastSurfaceSize.y = 0; - updateSurfaceSize(getPendingTransaction()); + updateSurfaceSize(t); } void updateSurfaceSize(SurfaceControl.Transaction transaction) { diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 498fc5c81a4c..88e9ae9179c9 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -963,6 +963,22 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } } + @Override + void migrateToNewSurfaceControl(SurfaceControl.Transaction t) { + super.migrateToNewSurfaceControl(t); + if (mAppAnimationLayer == null) { + return; + } + + // As TaskDisplayArea is getting a new surface, reparent and reorder the child surfaces. + t.reparent(mAppAnimationLayer, mSurfaceControl); + t.reparent(mBoostedAppAnimationLayer, mSurfaceControl); + t.reparent(mHomeAppAnimationLayer, mSurfaceControl); + t.reparent(mSplitScreenDividerAnchor, mSurfaceControl); + reassignLayer(t); + scheduleAnimation(); + } + void onRootTaskRemoved(Task rootTask) { if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) { Slog.v(TAG_ROOT_TASK, "onRootTaskRemoved: detaching " + rootTask + " from displayId=" diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 5d22f8fde057..375b3f49be13 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -310,7 +310,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { boolean taskAppearedSent = t.mTaskAppearedSent; if (taskAppearedSent) { if (t.getSurfaceControl() != null) { - t.migrateToNewSurfaceControl(); + t.migrateToNewSurfaceControl(t.getSyncTransaction()); } t.mTaskAppearedSent = false; } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 8d859584d5f5..e3679c0d8096 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -389,12 +389,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mParent.onChildAdded(this); } if (!mReparenting) { + onSyncReparent(oldParent, mParent); if (mParent != null && mParent.mDisplayContent != null && mDisplayContent != mParent.mDisplayContent) { onDisplayChanged(mParent.mDisplayContent); } onParentChanged(mParent, oldParent); - onSyncReparent(oldParent, mParent); } } @@ -460,8 +460,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * This is used to revoke control of the SurfaceControl from a client process that was * previously organizing this WindowContainer. */ - void migrateToNewSurfaceControl() { - SurfaceControl.Transaction t = getPendingTransaction(); + void migrateToNewSurfaceControl(SurfaceControl.Transaction t) { t.remove(mSurfaceControl); // Clear the last position so the new SurfaceControl will get correct position mLastSurfacePosition.set(0, 0); diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 53ebfb2c6e0e..8148f15981b3 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -451,24 +451,15 @@ public abstract class WindowManagerInternal { public abstract int getInputMethodWindowVisibleHeight(int displayId); /** - * Notifies WindowManagerService that the current IME window status is being changed. + * Notifies WindowManagerService that the expected back-button behavior might have changed. * * <p>Only {@link com.android.server.inputmethod.InputMethodManagerService} is the expected and * tested caller of this method.</p> * - * @param imeToken token to track the active input method. Corresponding IME windows can be - * identified by checking {@link android.view.WindowManager.LayoutParams#token}. - * Note that there is no guarantee that the corresponding window is already - * created - * @param imeWindowVisible whether the active IME thinks that its window should be visible or - * hidden, no matter how WindowManagerService will react / has reacted - * to corresponding API calls. Note that this state is not guaranteed - * to be synchronized with state in WindowManagerService. * @param dismissImeOnBackKeyPressed {@code true} if the software keyboard is shown and the back * key is expected to dismiss the software keyboard. */ - public abstract void updateInputMethodWindowStatus(@NonNull IBinder imeToken, - boolean imeWindowVisible, boolean dismissImeOnBackKeyPressed); + public abstract void setDismissImeOnBackKeyPressed(boolean dismissImeOnBackKeyPressed); /** * Notifies WindowManagerService that the current IME window status is being changed. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index d2c9e29f9d37..c5e000000eee 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1600,11 +1600,17 @@ public class WindowManagerService extends IWindowManager.Stub ProtoLog.w(WM_ERROR, "Attempted to add window with exiting application token " + ".%s Aborting.", token); return WindowManagerGlobal.ADD_APP_EXITING; - } else if (type == TYPE_APPLICATION_STARTING && activity.mStartingWindow != null) { - ProtoLog.w(WM_ERROR, - "Attempted to add starting window to token with already existing" - + " starting window"); - return WindowManagerGlobal.ADD_DUPLICATE_ADD; + } else if (type == TYPE_APPLICATION_STARTING) { + if (activity.mStartingWindow != null) { + ProtoLog.w(WM_ERROR, "Attempted to add starting window to " + + "token with already existing starting window"); + return WindowManagerGlobal.ADD_DUPLICATE_ADD; + } + if (activity.mStartingData == null) { + ProtoLog.w(WM_ERROR, "Attempted to add starting window to " + + "token but already cleaned"); + return WindowManagerGlobal.ADD_DUPLICATE_ADD; + } } } else if (rootType == TYPE_INPUT_METHOD) { if (token.windowType != TYPE_INPUT_METHOD) { @@ -7718,8 +7724,7 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void updateInputMethodWindowStatus(@NonNull IBinder imeToken, - boolean imeWindowVisible, boolean dismissImeOnBackKeyPressed) { + public void setDismissImeOnBackKeyPressed(boolean dismissImeOnBackKeyPressed) { mPolicy.setDismissImeOnBackKeyPressed(dismissImeOnBackKeyPressed); } diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp index a73f6c6d8c2d..6cb4a63a5636 100644 --- a/services/core/jni/com_android_server_SystemServer.cpp +++ b/services/core/jni/com_android_server_SystemServer.cpp @@ -123,16 +123,9 @@ static void android_server_SystemServer_initZygoteChildHeapProfiling(JNIEnv* /* } static void android_server_SystemServer_fdtrackAbort(JNIEnv*, jobject) { - raise(BIONIC_SIGNAL_FDTRACK); - - // Wait for a bit to allow fdtrack to dump backtraces to logcat. - std::this_thread::sleep_for(5s); - - // Abort on a different thread to avoid ART dumping runtime stacks. - std::thread([]() { - LOG_ALWAYS_FATAL("b/140703823: aborting due to fd leak: check logs for fd " - "backtraces"); - }).join(); + sigval val; + val.sival_int = 1; + sigqueue(getpid(), BIONIC_SIGNAL_FDTRACK, val); } static jlong android_server_SystemServer_startIncrementalService(JNIEnv* env, jclass klass, diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index e3fbeddc3a5f..db70d44d37f6 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -96,6 +96,10 @@ static const Constants& constants() { return c; } +static bool isPageAligned(IncFsSize s) { + return (s & (Constants::blockSize - 1)) == 0; +} + template <base::LogSeverity level = base::ERROR> bool mkdirOrLog(std::string_view name, int mode = 0770, bool allowExisting = true) { auto cstr = path::c_str(name); @@ -1001,25 +1005,53 @@ std::string IncrementalService::normalizePathToStorage(const IncFsMount& ifs, St int IncrementalService::makeFile(StorageId storage, std::string_view path, int mode, FileId id, incfs::NewFileParams params, std::span<const uint8_t> data) { - if (auto ifs = getIfs(storage)) { - std::string normPath = normalizePathToStorage(*ifs, storage, path); - if (normPath.empty()) { - LOG(ERROR) << "Internal error: storageId " << storage - << " failed to normalize: " << path; + const auto ifs = getIfs(storage); + if (!ifs) { + return -EINVAL; + } + if (data.size() > params.size) { + LOG(ERROR) << "Bad data size - bigger than file size"; + return -EINVAL; + } + if (!data.empty() && data.size() != params.size) { + // Writing a page is an irreversible operation, and it can't be updated with additional + // data later. Check that the last written page is complete, or we may break the file. + if (!isPageAligned(data.size())) { + LOG(ERROR) << "Bad data size - tried to write half a page?"; return -EINVAL; } - if (auto err = mIncFs->makeFile(ifs->control, normPath, mode, id, params); err) { - LOG(ERROR) << "Internal error: storageId " << storage << " failed to makeFile: " << err; - return err; + } + const std::string normPath = normalizePathToStorage(*ifs, storage, path); + if (normPath.empty()) { + LOG(ERROR) << "Internal error: storageId " << storage << " failed to normalize: " << path; + return -EINVAL; + } + if (auto err = mIncFs->makeFile(ifs->control, normPath, mode, id, params); err) { + LOG(ERROR) << "Internal error: storageId " << storage << " failed to makeFile: " << err; + return err; + } + if (params.size > 0) { + // Only v2+ incfs supports automatically trimming file over-reserved sizes + if (mIncFs->features() & incfs::Features::v2) { + if (auto err = mIncFs->reserveSpace(ifs->control, normPath, params.size)) { + if (err != -EOPNOTSUPP) { + LOG(ERROR) << "Failed to reserve space for a new file: " << err; + (void)mIncFs->unlink(ifs->control, normPath); + return err; + } else { + LOG(WARNING) << "Reserving space for backing file isn't supported, " + "may run out of disk later"; + } + } } if (!data.empty()) { if (auto err = setFileContent(ifs, id, path, data); err) { + (void)mIncFs->unlink(ifs->control, normPath); return err; } } - return 0; } - return -EINVAL; + return 0; } int IncrementalService::makeDir(StorageId storageId, std::string_view path, int mode) { @@ -1708,7 +1740,7 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_ } const auto entryUncompressed = entry.method == kCompressStored; - const auto entryPageAligned = (entry.offset & (constants().blockSize - 1)) == 0; + const auto entryPageAligned = isPageAligned(entry.offset); if (!extractNativeLibs) { // ensure the file is properly aligned and unpacked diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp index 80f409ff1c61..2a061226b713 100644 --- a/services/incremental/ServiceWrappers.cpp +++ b/services/incremental/ServiceWrappers.cpp @@ -209,6 +209,10 @@ public: ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const final { return incfs::writeBlocks({blocks.data(), size_t(blocks.size())}); } + ErrorCode reserveSpace(const Control& control, std::string_view path, + IncFsSize size) const final { + return incfs::reserveSpace(control, path, size); + } WaitResult waitForPendingReads(const Control& control, std::chrono::milliseconds timeout, std::vector<incfs::ReadInfo>* pendingReadsBuffer) const final { return incfs::waitForPendingReads(control, timeout, pendingReadsBuffer); diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h index d113f992de71..231b76ff1701 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -107,6 +107,8 @@ public: virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0; virtual UniqueFd openForSpecialOps(const Control& control, FileId id) const = 0; virtual ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const = 0; + virtual ErrorCode reserveSpace(const Control& control, std::string_view path, + IncFsSize size) const = 0; virtual WaitResult waitForPendingReads( const Control& control, std::chrono::milliseconds timeout, std::vector<incfs::ReadInfo>* pendingReadsBuffer) const = 0; diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index bf798273a8a9..45b796bf4704 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -372,6 +372,8 @@ public: MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path)); MOCK_CONST_METHOD2(openForSpecialOps, UniqueFd(const Control& control, FileId id)); MOCK_CONST_METHOD1(writeBlocks, ErrorCode(std::span<const DataBlock> blocks)); + MOCK_CONST_METHOD3(reserveSpace, + ErrorCode(const Control& control, std::string_view path, IncFsSize size)); MOCK_CONST_METHOD3(waitForPendingReads, WaitResult(const Control& control, std::chrono::milliseconds timeout, std::vector<incfs::ReadInfo>* pendingReadsBuffer)); @@ -379,7 +381,10 @@ public: ErrorCode(const Control& control, const std::vector<PerUidReadTimeouts>& perUidReadTimeouts)); - MockIncFs() { ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return()); } + MockIncFs() { + ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return()); + ON_CALL(*this, reserveSpace(_, _, _)).WillByDefault(Return(0)); + } void makeFileFails() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(-1)); } void makeFileSuccess() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(0)); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 4e23609bd774..6f71e991bb96 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -390,8 +390,6 @@ public final class SystemServer implements Dumpable { private static final String UNCRYPT_PACKAGE_FILE = "/cache/recovery/uncrypt_file"; private static final String BLOCK_MAP_FILE = "/cache/recovery/block.map"; - private static final String GSI_RUNNING_PROP = "ro.gsid.image_running"; - // maximum number of binder threads used for system_server // will be higher than the system default private static final int sMaxBinderThreads = 31; @@ -1327,7 +1325,7 @@ public final class SystemServer implements Dumpable { false); boolean enableLeftyService = SystemProperties.getBoolean("config.enable_lefty", false); - boolean isEmulator = SystemProperties.get("ro.kernel.qemu").equals("1"); + boolean isEmulator = SystemProperties.get("ro.boot.qemu").equals("1"); boolean isWatch = context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_WATCH); @@ -1662,8 +1660,7 @@ public final class SystemServer implements Dumpable { t.traceEnd(); final boolean hasPdb = !SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals(""); - final boolean hasGsi = SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0; - if (hasPdb && !hasGsi) { + if (hasPdb) { t.traceBegin("StartPersistentDataBlock"); mSystemServiceManager.startService(PersistentDataBlockService.class); t.traceEnd(); diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp index 334e53a3aec7..988c02bfb3db 100644 --- a/services/tests/PackageManagerServiceTests/unit/Android.bp +++ b/services/tests/PackageManagerServiceTests/unit/Android.bp @@ -23,14 +23,17 @@ package { android_test { name: "PackageManagerServiceUnitTests", - srcs: ["src/**/*.kt"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], static_libs: [ "androidx.test.rules", "androidx.test.runner", "junit", + "kotlin-test", "services.core", "servicestests-utils", - "testng", "truth-prebuilt", ], platform_apis: true, diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt index 0fa9a1def381..d5eda203e42f 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt @@ -25,8 +25,8 @@ import android.os.PatternMatcher import android.util.ArraySet import com.android.server.SystemConfig import com.android.server.compat.PlatformCompat -import com.android.server.pm.verify.domain.DomainVerificationCollector import com.android.server.pm.parsing.pkg.AndroidPackage +import com.android.server.pm.verify.domain.DomainVerificationCollector import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertThat diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt index 8ef92393242a..9693f3bab127 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt @@ -19,6 +19,7 @@ package com.android.server.pm.test.verify.domain import android.content.pm.verify.domain.DomainSet import android.content.pm.verify.domain.DomainVerificationInfo import android.content.pm.verify.domain.DomainVerificationRequest +import android.content.pm.verify.domain.DomainVerificationState import android.content.pm.verify.domain.DomainVerificationUserState import android.os.Parcel import android.os.Parcelable @@ -74,7 +75,9 @@ class DomainVerificationCoreApiTest { DomainVerificationInfo( UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"), "com.test.pkg", - massiveSet.withIndex().associate { it.value to it.index } + massiveSet.withIndex().associate { + it.value to DomainVerificationState.convertToInfoState(it.index) + } ) }, unparcel = { DomainVerificationInfo.CREATOR.createFromParcel(it) }, diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index 53f0ca20e787..7e25901301aa 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -20,20 +20,21 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.pm.PackageUserState -import android.content.pm.verify.domain.DomainVerificationManager import android.content.pm.parsing.component.ParsedActivity import android.content.pm.parsing.component.ParsedIntentInfo +import android.content.pm.verify.domain.DomainVerificationManager +import android.content.pm.verify.domain.DomainVerificationState import android.os.Build import android.os.Process import android.util.ArraySet import android.util.SparseArray import androidx.test.platform.app.InstrumentationRegistry import com.android.server.pm.PackageSetting +import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.verify.domain.DomainVerificationEnforcer import com.android.server.pm.verify.domain.DomainVerificationManagerInternal import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy -import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.spyThrowOnUnmocked import com.android.server.testutils.whenever @@ -50,6 +51,8 @@ import java.io.File import java.util.UUID import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger +import kotlin.test.assertFailsWith +import kotlin.test.fail @RunWith(Parameterized::class) class DomainVerificationEnforcerTest { @@ -81,47 +84,49 @@ class DomainVerificationEnforcerTest { whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) { true } + whenever(doesUserExist(anyInt())) { (arguments[0] as Int) <= 1 } }) } } - val makeService: (Context) -> Triple<AtomicInteger, AtomicInteger, DomainVerificationService> = - { - val callingUidInt = AtomicInteger(-1) - val callingUserIdInt = AtomicInteger(-1) - - val connection: DomainVerificationManagerInternal.Connection = - mockThrowOnUnmocked { - whenever(callingUid) { callingUidInt.get() } - whenever(callingUserId) { callingUserIdInt.get() } - whenever(getPackageSettingLocked(VISIBLE_PKG)) { visiblePkgSetting } - whenever(getPackageLocked(VISIBLE_PKG)) { visiblePkg } - whenever(getPackageSettingLocked(INVISIBLE_PKG)) { invisiblePkgSetting } - whenever(getPackageLocked(INVISIBLE_PKG)) { invisiblePkg } - whenever(schedule(anyInt(), any())) - whenever(scheduleWriteSettings()) - whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false } - whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) { - true - } + val makeService: (Context) -> Triple<AtomicInteger, AtomicInteger, + DomainVerificationService> = { + val callingUidInt = AtomicInteger(-1) + val callingUserIdInt = AtomicInteger(-1) + + val connection: DomainVerificationManagerInternal.Connection = + mockThrowOnUnmocked { + whenever(callingUid) { callingUidInt.get() } + whenever(callingUserId) { callingUserIdInt.get() } + whenever(getPackageSettingLocked(VISIBLE_PKG)) { visiblePkgSetting } + whenever(getPackageLocked(VISIBLE_PKG)) { visiblePkg } + whenever(getPackageSettingLocked(INVISIBLE_PKG)) { invisiblePkgSetting } + whenever(getPackageLocked(INVISIBLE_PKG)) { invisiblePkg } + whenever(schedule(anyInt(), any())) + whenever(scheduleWriteSettings()) + whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false } + whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) { + true } - val service = DomainVerificationService( - it, - mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } }, - mockThrowOnUnmocked { - whenever( - isChangeEnabled( - anyLong(), - any() - ) - ) { true } - }).apply { - setConnection(connection) + whenever(doesUserExist(anyInt())) { (arguments[0] as Int) <= 1 } } - - Triple(callingUidInt, callingUserIdInt, service) + val service = DomainVerificationService( + it, + mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } }, + mockThrowOnUnmocked { + whenever( + isChangeEnabled( + anyLong(), + any() + ) + ) { true } + }).apply { + setConnection(connection) } + Triple(callingUidInt, callingUserIdInt, service) + } + fun enforcer( type: Type, name: String, @@ -175,7 +180,7 @@ class DomainVerificationEnforcerTest { service(Type.INTERNAL, "setStatusInternalPackageName") { setDomainVerificationStatusInternal( it.targetPackageName, - DomainVerificationManager.STATE_SUCCESS, + DomainVerificationState.STATE_SUCCESS, ArraySet(setOf("example.com")) ) }, @@ -206,7 +211,7 @@ class DomainVerificationEnforcerTest { setDomainVerificationStatus( it.targetDomainSetId, setOf("example.com"), - DomainVerificationManager.STATE_SUCCESS + DomainVerificationState.STATE_SUCCESS ) }, service(Type.VERIFIER, "setStatusInternalUid") { @@ -214,7 +219,7 @@ class DomainVerificationEnforcerTest { it.callingUid, it.targetDomainSetId, setOf("example.com"), - DomainVerificationManager.STATE_SUCCESS + DomainVerificationState.STATE_SUCCESS ) }, service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") { @@ -475,24 +480,7 @@ class DomainVerificationEnforcerTest { fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { // User selector makes no distinction by UID val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID - if (throws) { - allUids.forEach { - assertFails { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - } else { - allUids.forEach { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - - // User selector doesn't use QUERY_ALL, so the invisible package should always fail - allUids.forEach { - assertFails { - runMethod(target, it, visible = false, callingUserId, targetUserId) - } - } + runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws) } val callingUserId = 0 @@ -529,24 +517,7 @@ class DomainVerificationEnforcerTest { fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { // User selector makes no distinction by UID val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID - if (throws) { - allUids.forEach { - assertFails { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - } else { - allUids.forEach { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - - // User selector doesn't use QUERY_ALL, so the invisible package should always fail - allUids.forEach { - assertFails { - runMethod(target, it, visible = false, callingUserId, targetUserId) - } - } + runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws) } val callingUserId = 0 @@ -590,24 +561,10 @@ class DomainVerificationEnforcerTest { fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { // Legacy makes no distinction by UID val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID - if (throws) { - allUids.forEach { - assertFails { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - } else { - allUids.forEach { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - - // Legacy doesn't use QUERY_ALL, so the invisible package should always fail - allUids.forEach { - assertFails { - runMethod(target, it, visible = false, callingUserId, targetUserId) - } - } + // The legacy selector does a silent failure when the user IDs don't match, so it + // cannot verify the non-existent user ID check, as it will not throw an Exception. + runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws, + verifyUserIdCheck = false) } val callingUserId = 0 @@ -642,27 +599,29 @@ class DomainVerificationEnforcerTest { } val target = params.construct(context) - fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { - // Legacy makes no distinction by UID - val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID - if (throws) { - allUids.forEach { - assertFails { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - } else { - allUids.forEach { - runMethod(target, it, visible = true, callingUserId, targetUserId) + // Legacy code can return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED + // as an error code. This is distinct from the class level assertFails as unfortunately + // the same number, 0, is used in opposite contexts, where it does represent a failure + // for this legacy case, but not for the modern APIs. + fun assertFailsLegacy(block: () -> Any?) { + try { + val value = block() + if ((value as? Int) + != PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED + ) { + throw AssertionError("Expected call to return false, was $value") } + } catch (e: SecurityException) { + } catch (e: PackageManager.NameNotFoundException) { + // Any of these 2 exceptions are considered failures, which is expected } + } - // Legacy doesn't use QUERY_ALL, so the invisible package should always fail - allUids.forEach { - assertFails { - runMethod(target, it, visible = false, callingUserId, targetUserId) - } - } + fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { + // Legacy makes no distinction by UID + val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID + runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws, + assertFailsMethod = ::assertFailsLegacy) } val callingUserId = 0 @@ -704,17 +663,8 @@ class DomainVerificationEnforcerTest { fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { // Owner querent makes no distinction by UID val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID - if (throws) { - allUids.forEach { - assertFails { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - } else { - allUids.forEach { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } + runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws, + verifyInvisiblePkg = false) } val callingUserId = 0 @@ -782,22 +732,88 @@ class DomainVerificationEnforcerTest { return params.runMethod(target, callingUid, callingUserId, userId, packageName, uuid, proxy) } + private fun runCrossUserMethod( + allUids: Iterable<Int>, + target: Any, + callingUserId: Int, + targetUserId: Int, + throws: Boolean, + verifyUserIdCheck: Boolean = true, + verifyInvisiblePkg: Boolean = true, + assertFailsMethod: (() -> Any?) -> Unit = ::assertFails, + ) { + if (throws) { + allUids.forEach { + assertFailsMethod { + // When testing a non-user ID failure, send an invalid user ID. + // This ensures the failure occurs before the user ID check is run. + try { + runMethod(target, it, visible = true, callingUserId, 100) + } catch (e: SecurityException) { + if (verifyUserIdCheck) { + e.message?.let { + if (it.contains("user ID", ignoreCase = true) + || it.contains("100")) { + fail( + "Method should not check user existence before permissions" + ) + } + } + } + + // Rethrow to allow normal fail checking logic to run + throw e + } + } + } + } else { + allUids.forEach { + runMethod(target, it, visible = true, callingUserId, targetUserId) + } + } + + if (verifyInvisiblePkg) { + allUids.forEach { + assertFailsMethod { + runMethod(target, it, visible = false, callingUserId, targetUserId) + } + } + } + + if (verifyUserIdCheck) { + // An invalid target user ID should always fail + allUids.forEach { + assertFailsWith(SecurityException::class) { + runMethod(target, it, visible = true, callingUserId, 100) + } + } + + // An invalid calling user ID should always fail, although this cannot happen in prod + allUids.forEach { + assertFailsWith(SecurityException::class) { + runMethod(target, it, visible = true, 100, targetUserId) + } + } + } + } + private fun assertFails(block: () -> Any?) { try { val value = block() - // Some methods return false rather than throwing, so check that as well - if ((value as? Boolean) != false) { - // Can also return default value if it's a legacy call - if ((value as? Int) - != PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED - ) { - throw AssertionError("Expected call to return false, was $value") - } + // Some methods return false or an error rather than throwing, so check that as well + val valueAsBoolean = value as? Boolean + if (valueAsBoolean == false) { + // Expected failure, do not throw + return + } + + val valueAsInt = value as? Int + if (valueAsInt != null && valueAsInt == DomainVerificationManager.STATUS_OK) { + throw AssertionError("Expected call to return false, was $value") } } catch (e: SecurityException) { } catch (e: PackageManager.NameNotFoundException) { - } catch (e: DomainVerificationManager.InvalidDomainSetException) { - // Any of these 3 exceptions are considered failures, which is expected + // Any of these 2 exceptions are considered failures, which is expected } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java new file mode 100644 index 000000000000..8ae4c5ae96a3 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.test.verify.domain; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.pm.PackageManager; + +import com.android.server.pm.verify.domain.DomainVerificationService; + +import java.util.Set; +import java.util.UUID; + +/** + * Proxies Kotlin calls to the Java layer such that null values can be passed for {@link NonNull} + * marked parameters, as Kotlin disallows this at the compiler leveling, preventing the null error + * codes from being tested. + */ +class DomainVerificationJavaUtil { + + static int setStatusForceNullable(@NonNull DomainVerificationService service, + @Nullable UUID domainSetId, @Nullable Set<String> domains, int state) + throws PackageManager.NameNotFoundException { + return service.setDomainVerificationStatus(domainSetId, domains, state); + } + + static int setUserSelectionForceNullable(@NonNull DomainVerificationService service, + @Nullable UUID domainSetId, @Nullable Set<String> domains, boolean enabled, + @UserIdInt int userId) + throws PackageManager.NameNotFoundException { + return service.setDomainVerificationUserSelection(domainSetId, domains, enabled, userId); + } +} diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt index 9a3bd994eac0..509824d93512 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt @@ -19,9 +19,9 @@ package com.android.server.pm.test.verify.domain import android.content.pm.IntentFilterVerificationInfo import android.content.pm.PackageManager import android.util.ArraySet -import com.android.server.pm.verify.domain.DomainVerificationLegacySettings import com.android.server.pm.test.verify.domain.DomainVerificationPersistenceTest.Companion.readXml import com.android.server.pm.test.verify.domain.DomainVerificationPersistenceTest.Companion.writeXml +import com.android.server.pm.verify.domain.DomainVerificationLegacySettings import com.google.common.truth.Truth.assertWithMessage import org.junit.Rule import org.junit.Test diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt new file mode 100644 index 000000000000..0e74b65d25d5 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.test.verify.domain + +import android.content.Intent +import android.content.pm.PackageManager +import android.content.pm.parsing.component.ParsedActivity +import android.content.pm.parsing.component.ParsedIntentInfo +import android.content.pm.verify.domain.DomainVerificationInfo +import android.content.pm.verify.domain.DomainVerificationManager +import android.content.pm.verify.domain.DomainVerificationUserState +import android.os.Build +import android.os.PatternMatcher +import android.os.Process +import android.util.ArraySet +import com.android.server.pm.PackageSetting +import com.android.server.pm.parsing.pkg.AndroidPackage +import com.android.server.pm.verify.domain.DomainVerificationService +import com.android.server.testutils.mockThrowOnUnmocked +import com.android.server.testutils.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong +import org.mockito.ArgumentMatchers.anyString +import java.util.UUID +import kotlin.test.assertFailsWith + +class DomainVerificationManagerApiTest { + + companion object { + private const val PKG_ONE = "com.test.one" + private const val PKG_TWO = "com.test.two" + private const val PKG_THREE = "com.test.three" + + private val UUID_ONE = UUID.fromString("1b041c96-8d37-4932-a858-561bfac5947c") + private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c") + private val UUID_THREE = UUID.fromString("0b3260ed-07c4-4b45-840b-237f8fb8b433") + private val UUID_INVALID = UUID.fromString("ad33babc-490b-4965-9d78-7e91248b00f") + + private val DOMAIN_BASE = DomainVerificationManagerApiTest::class.java.packageName + private val DOMAIN_1 = "one.$DOMAIN_BASE" + private val DOMAIN_2 = "two.$DOMAIN_BASE" + private val DOMAIN_3 = "three.$DOMAIN_BASE" + private val DOMAIN_4 = "four.$DOMAIN_BASE" + } + + @Test + fun queryValidVerificationPackageNames() { + val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList()) + + val service = makeService(pkgWithDomains, pkgWithoutDomains).apply { + addPackages(pkgWithDomains, pkgWithoutDomains) + } + + assertThat(service.queryValidVerificationPackageNames()) + .containsExactly(pkgWithDomains.getName()) + } + + @Test + fun getDomainVerificationInfoId() { + val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList()) + + val service = makeService(pkgWithDomains, pkgWithoutDomains).apply { + addPackages(pkgWithDomains, pkgWithoutDomains) + } + + assertThat(service.getDomainVerificationInfoId(PKG_ONE)).isEqualTo(UUID_ONE) + assertThat(service.getDomainVerificationInfoId(PKG_TWO)).isEqualTo(UUID_TWO) + + assertThat(service.getDomainVerificationInfoId("invalid.pkg.name")).isEqualTo(null) + } + + @Test + fun getDomainVerificationInfo() { + val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList()) + + val service = makeService(pkgWithDomains, pkgWithoutDomains).apply { + addPackages(pkgWithDomains, pkgWithoutDomains) + } + + val infoOne = service.getDomainVerificationInfo(pkgWithDomains.getName()) + assertThat(infoOne).isNotNull() + assertThat(infoOne!!.identifier).isEqualTo(pkgWithDomains.domainSetId) + assertThat(infoOne.packageName).isEqualTo(pkgWithDomains.getName()) + assertThat(infoOne.hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DomainVerificationInfo.STATE_NO_RESPONSE, + DOMAIN_2 to DomainVerificationInfo.STATE_NO_RESPONSE, + )) + + assertThat(service.getDomainVerificationInfo(pkgWithoutDomains.getName())).isNull() + + assertFailsWith(PackageManager.NameNotFoundException::class) { + service.getDomainVerificationInfo("invalid.pkg.name") + } + } + + @Test + fun setStatus() { + val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_3, DOMAIN_4)) + + val map = mutableMapOf(pkg1.getName() to pkg1, pkg2.getName() to pkg2) + val service = makeService(map::get).apply { addPackages(pkg1, pkg2) } + + assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_2), 1100)) + .isEqualTo(DomainVerificationManager.STATUS_OK) + + assertThat(service.setStatus(UUID_INVALID, setOf(DOMAIN_1), 1100)) + .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID) + + assertThat(DomainVerificationJavaUtil.setStatusForceNullable(service, null, + setOf(DOMAIN_1), 1100)) + .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL) + + assertThat(DomainVerificationJavaUtil.setStatusForceNullable(service, UUID_ONE, null, + 1100)).isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY) + + assertThat(service.setStatus(UUID_ONE, emptySet(), 1100)) + .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY) + + assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_3), 1100)) + .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN) + + assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1, DOMAIN_2, DOMAIN_3), 1100)) + .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN) + + assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), 15)) + .isEqualTo(DomainVerificationManager.ERROR_INVALID_STATE_CODE) + + map.clear() + assertFailsWith(PackageManager.NameNotFoundException::class){ + service.setStatus(UUID_ONE, setOf(DOMAIN_1), 1100) + } + } + + @Test + fun setDomainVerificationLinkHandlingAllowed() { + val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_3, DOMAIN_4)) + + val map = mutableMapOf(pkg1.getName() to pkg1, pkg2.getName() to pkg2) + val service = makeService(map::get).apply { addPackages(pkg1, pkg2) } + + service.setDomainVerificationLinkHandlingAllowed(PKG_ONE, false, 0); + + // Should edit same package, same user + assertThat(service.getDomainVerificationUserState(PKG_ONE, 0) + ?.isLinkHandlingAllowed).isEqualTo(false) + + // Shouldn't edit different user + assertThat(service.getDomainVerificationUserState(PKG_ONE, 1) + ?.isLinkHandlingAllowed).isEqualTo(true) + + // Shouldn't edit different package + assertThat(service.getDomainVerificationUserState(PKG_TWO, 0) + ?.isLinkHandlingAllowed).isEqualTo(true) + + assertFailsWith(PackageManager.NameNotFoundException::class){ + service.setDomainVerificationLinkHandlingAllowed("invalid.pkg.name", false, 0); + } + } + + @Test + fun setUserSelection() { + val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_3, DOMAIN_4)) + val pkg3 = mockPkgSetting(PKG_THREE, UUID_THREE, listOf(DOMAIN_1, DOMAIN_2)) + + val map = mutableMapOf( + pkg1.getName() to pkg1, + pkg2.getName() to pkg2, + pkg3.getName() to pkg3 + ) + val service = makeService(map::get).apply { addPackages(pkg1, pkg2, pkg3) } + + assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, 0)) + .isEqualTo(DomainVerificationManager.STATUS_OK) + + assertThat(service.setUserSelection(UUID_INVALID, setOf(DOMAIN_1), true, 0)) + .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID) + + assertThat(DomainVerificationJavaUtil.setUserSelectionForceNullable(service, null, + setOf(DOMAIN_1), true, 0)) + .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL) + + assertThat(DomainVerificationJavaUtil.setUserSelectionForceNullable(service, UUID_ONE, null, + true, 0)).isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY) + + assertThat(service.setUserSelection(UUID_ONE, emptySet(), true, 0)) + .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY) + + assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_3), true, 0)) + .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN) + + assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_1, DOMAIN_2, DOMAIN_3), true, 0)) + .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN) + + service.setStatus(UUID_ONE, setOf(DOMAIN_2), DomainVerificationInfo.STATE_SUCCESS) + + assertThat(service.setUserSelection(UUID_THREE, setOf(DOMAIN_2), true, 0)) + .isEqualTo(DomainVerificationManager.ERROR_UNABLE_TO_APPROVE) + + map.clear() + assertFailsWith(PackageManager.NameNotFoundException::class){ + service.setUserSelection(UUID_ONE, setOf(DOMAIN_1), true, 0) + } + } + + @Test + fun getDomainVerificationUserState() { + val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList()) + + val service = makeService(pkgWithDomains, pkgWithoutDomains).apply { + addPackages(pkgWithDomains, pkgWithoutDomains) + } + + val infoOne = service.getDomainVerificationUserState(pkgWithDomains.getName(), 0) + assertThat(infoOne).isNotNull() + assertThat(infoOne!!.identifier).isEqualTo(pkgWithDomains.domainSetId) + assertThat(infoOne.packageName).isEqualTo(pkgWithDomains.getName()) + assertThat(infoOne.isLinkHandlingAllowed).isTrue() + assertThat(infoOne.hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DomainVerificationUserState.DOMAIN_STATE_NONE, + DOMAIN_2 to DomainVerificationUserState.DOMAIN_STATE_NONE, + )) + + val infoTwo = service.getDomainVerificationUserState(pkgWithoutDomains.getName(), 0) + assertThat(infoTwo).isNotNull() + assertThat(infoTwo!!.identifier).isEqualTo(pkgWithoutDomains.domainSetId) + assertThat(infoTwo.packageName).isEqualTo(pkgWithoutDomains.getName()) + assertThat(infoOne.isLinkHandlingAllowed).isTrue() + assertThat(infoTwo.hostToStateMap).isEmpty() + + assertFailsWith(PackageManager.NameNotFoundException::class) { + service.getDomainVerificationUserState("invalid.pkg.name", 0) + } + } + + @Test + fun getOwnersForDomain() { + val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2)) + + val service = makeService(pkg1, pkg2).apply { + addPackages(pkg1, pkg2) + } + + assertThat(service.getOwnersForDomain(DOMAIN_1, 0)).isEmpty() + + service.setStatus(pkg1.domainSetId, setOf(DOMAIN_1), DomainVerificationInfo.STATE_SUCCESS) + + service.setStatus(pkg2.domainSetId, setOf(DOMAIN_1), DomainVerificationInfo.STATE_SUCCESS) + + service.setUserSelection(pkg1.domainSetId, setOf(DOMAIN_2), true, 0) + + service.getOwnersForDomain(DOMAIN_1, 0).let { + assertThat(it).hasSize(2) + assertThat(it[0].packageName).isEqualTo(pkg1.getName()) + assertThat(it[0].isOverrideable).isEqualTo(false) + assertThat(it[1].packageName).isEqualTo(pkg2.getName()) + assertThat(it[1].isOverrideable).isEqualTo(false) + } + + service.getOwnersForDomain(DOMAIN_2, 0).let { + assertThat(it).hasSize(1) + assertThat(it.single().packageName).isEqualTo(pkg1.getName()) + assertThat(it.single().isOverrideable).isEqualTo(true) + } + assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty() + + service.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_2), true, 0) + service.getOwnersForDomain(DOMAIN_2, 0).let { + assertThat(it).hasSize(1) + assertThat(it.single().packageName).isEqualTo(pkg2.getName()) + assertThat(it.single().isOverrideable).isEqualTo(true) + } + assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty() + } + + private fun makeService(vararg pkgSettings: PackageSetting) = + makeService { pkgName -> pkgSettings.find { pkgName == it.getName() } } + + private fun makeService(pkgSettingFunction: (String) -> PackageSetting? = { null }) = + DomainVerificationService(mockThrowOnUnmocked { + // Assume the test has every permission necessary + whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString())) + whenever(checkPermission(anyString(), anyInt(), anyInt())) { + PackageManager.PERMISSION_GRANTED + } + }, mockThrowOnUnmocked { + whenever(linkedApps) { ArraySet<String>() } + }, mockThrowOnUnmocked { + whenever(isChangeEnabled(anyLong(), any())) { true } + }).apply { + setConnection(mockThrowOnUnmocked { + whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false } + whenever(doesUserExist(0)) { true } + whenever(doesUserExist(1)) { true } + whenever(scheduleWriteSettings()) + + // Need to provide an internal UID so some permission checks are ignored + whenever(callingUid) { Process.ROOT_UID } + whenever(callingUserId) { 0 } + + whenever(getPackageSettingLocked(anyString())) { + pkgSettingFunction(arguments[0] as String) + } + whenever(getPackageLocked(anyString())) { + pkgSettingFunction(arguments[0] as String)?.getPkg() + } + }) + } + + private fun mockPkgSetting(pkgName: String, domainSetId: UUID, domains: List<String> = listOf( + DOMAIN_1, DOMAIN_2 + )) = mockThrowOnUnmocked<PackageSetting> { + val pkg = mockThrowOnUnmocked<AndroidPackage> { + whenever(packageName) { pkgName } + whenever(targetSdkVersion) { Build.VERSION_CODES.S } + + val activityList = listOf( + ParsedActivity().apply { + domains.forEach { + addIntent( + ParsedIntentInfo().apply { + autoVerify = true + addAction(Intent.ACTION_VIEW) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataScheme("http") + addDataScheme("https") + addDataPath("/sub", PatternMatcher.PATTERN_LITERAL) + addDataAuthority(it, null) + } + ) + } + }, + ) + + whenever(activities) { activityList } + } + + whenever(getPkg()) { pkg } + whenever(getName()) { pkgName } + whenever(this.domainSetId) { domainSetId } + whenever(getInstantApp(anyInt())) { false } + whenever(firstInstallTime) { 0L } + } + + fun DomainVerificationService.addPackages(vararg pkgSettings: PackageSetting) = + pkgSettings.forEach(::addPackage) +} diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt index 8c31c65e1b0a..a5db3c578670 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt @@ -16,8 +16,8 @@ package com.android.server.pm.test.verify.domain -import android.content.pm.verify.domain.DomainVerificationRequest import android.content.pm.verify.domain.DomainVerificationInfo +import android.content.pm.verify.domain.DomainVerificationRequest import android.content.pm.verify.domain.DomainVerificationUserState import com.android.server.pm.verify.domain.DomainVerificationPersistence diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt new file mode 100644 index 000000000000..fe3672d06bc0 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.test.verify.domain + +import android.content.Intent +import android.content.pm.PackageManager +import android.content.pm.parsing.component.ParsedActivity +import android.content.pm.parsing.component.ParsedIntentInfo +import android.content.pm.verify.domain.DomainVerificationInfo.STATE_NO_RESPONSE +import android.content.pm.verify.domain.DomainVerificationInfo.STATE_SUCCESS +import android.content.pm.verify.domain.DomainVerificationInfo.STATE_UNMODIFIABLE +import android.content.pm.verify.domain.DomainVerificationManager +import android.content.pm.verify.domain.DomainVerificationState +import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_NONE +import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_SELECTED +import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_VERIFIED +import android.os.Build +import android.os.PatternMatcher +import android.os.Process +import android.util.ArraySet +import android.util.Xml +import com.android.server.pm.PackageSetting +import com.android.server.pm.parsing.pkg.AndroidPackage +import com.android.server.pm.verify.domain.DomainVerificationService +import com.android.server.testutils.mockThrowOnUnmocked +import com.android.server.testutils.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import java.util.UUID + +class DomainVerificationPackageTest { + + companion object { + private const val PKG_ONE = "com.test.one" + private const val PKG_TWO = "com.test.two" + private val UUID_ONE = UUID.fromString("1b041c96-8d37-4932-a858-561bfac5947c") + private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c") + + private val DOMAIN_BASE = DomainVerificationPackageTest::class.java.packageName + private val DOMAIN_1 = "one.$DOMAIN_BASE" + private val DOMAIN_2 = "two.$DOMAIN_BASE" + private val DOMAIN_3 = "three.$DOMAIN_BASE" + private val DOMAIN_4 = "four.$DOMAIN_BASE" + + private const val USER_ID = 0 + } + + private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE) + private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO) + + @Test + fun addPackageFirstTime() { + val service = makeService(pkg1, pkg2) + service.addPackage(pkg1) + val info = service.getInfo(pkg1.getName()) + assertThat(info.packageName).isEqualTo(pkg1.getName()) + assertThat(info.identifier).isEqualTo(pkg1.domainSetId) + assertThat(info.hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_NO_RESPONSE, + DOMAIN_2 to STATE_NO_RESPONSE, + )) + + val userState = service.getUserState(pkg1.getName()) + assertThat(userState.packageName).isEqualTo(pkg1.getName()) + assertThat(userState.identifier).isEqualTo(pkg1.domainSetId) + assertThat(userState.isLinkHandlingAllowed).isEqualTo(true) + assertThat(userState.user.identifier).isEqualTo(USER_ID) + assertThat(userState.hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_NONE, + DOMAIN_2 to DOMAIN_STATE_NONE, + )) + + assertThat(service.queryValidVerificationPackageNames()) + .containsExactly(pkg1.getName()) + } + + @Test + fun addPackageActive() { + // language=XML + val xml = """ + <?xml?> + <domain-verifications> + <active> + <package-state + packageName="${pkg1.getName()}" + id="${pkg1.domainSetId}" + > + <state> + <domain name="$DOMAIN_1" state="$STATE_SUCCESS"/> + </state> + <user-states> + <user-state userId="$USER_ID" allowLinkHandling="false"> + <enabled-hosts> + <host name="$DOMAIN_2"/> + </enabled-hosts> + </user-state> + </user-states> + </package-state> + </active> + </domain-verifications> + """.trimIndent() + + val service = makeService(pkg1, pkg2) + xml.byteInputStream().use { + service.readSettings(Xml.resolvePullParser(it)) + } + + service.addPackage(pkg1) + + val info = service.getInfo(pkg1.getName()) + assertThat(info.packageName).isEqualTo(pkg1.getName()) + assertThat(info.identifier).isEqualTo(pkg1.domainSetId) + assertThat(info.hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_SUCCESS, + DOMAIN_2 to STATE_NO_RESPONSE, + )) + + val userState = service.getUserState(pkg1.getName()) + assertThat(userState.packageName).isEqualTo(pkg1.getName()) + assertThat(userState.identifier).isEqualTo(pkg1.domainSetId) + assertThat(userState.isLinkHandlingAllowed).isEqualTo(false) + assertThat(userState.user.identifier).isEqualTo(USER_ID) + assertThat(userState.hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + )) + + assertThat(service.queryValidVerificationPackageNames()) + .containsExactly(pkg1.getName()) + } + + @Test + fun migratePackageDropDomain() { + val pkgName = PKG_ONE + val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, + listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3, DOMAIN_4)) + val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, + listOf(DOMAIN_1, DOMAIN_2)) + + // Test 4 domains: + // 1 will be approved and preserved, 2 will be selected and preserved, + // 3 will be denied and dropped, 4 will be selected and dropped + + val map = mutableMapOf<String, PackageSetting>() + val service = makeService { map[it] } + service.addPackage(pkgBefore) + + // Only insert the package after addPackage call to ensure the service doesn't access + // a live package inside the addPackage logic. It should only use the provided input. + map[pkgName] = pkgBefore + + // To test the approve/denial states, use the internal methods for this variant + service.setDomainVerificationStatusInternal(pkgName, DomainVerificationState.STATE_APPROVED, + ArraySet(setOf(DOMAIN_1))) + service.setDomainVerificationStatusInternal(pkgName, DomainVerificationState.STATE_DENIED, + ArraySet(setOf(DOMAIN_3))) + service.setUserSelection( + UUID_ONE, setOf(DOMAIN_2, DOMAIN_4), true, USER_ID) + + // Check the verifier cannot change the shell approve/deny states + service.setStatus(UUID_ONE, setOf(DOMAIN_1, DOMAIN_3), STATE_SUCCESS) + + assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_UNMODIFIABLE, + DOMAIN_2 to STATE_NO_RESPONSE, + DOMAIN_3 to STATE_UNMODIFIABLE, + DOMAIN_4 to STATE_NO_RESPONSE, + )) + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + DOMAIN_3 to DOMAIN_STATE_NONE, + DOMAIN_4 to DOMAIN_STATE_SELECTED, + )) + + // Now remove the package because migrateState shouldn't use it either + map.remove(pkgName) + + map[pkgName] = pkgAfter + + service.migrateState(pkgBefore, pkgAfter) + + assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_UNMODIFIABLE, + DOMAIN_2 to STATE_NO_RESPONSE, + )) + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + )) + assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName) + } + + @Test + fun migratePackageDropAll() { + val pkgName = PKG_ONE + val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, emptyList()) + + val map = mutableMapOf<String, PackageSetting>() + val service = makeService { map[it] } + service.addPackage(pkgBefore) + + // Only insert the package after addPackage call to ensure the service doesn't access + // a live package inside the addPackage logic. It should only use the provided input. + map[pkgName] = pkgBefore + + assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_NO_RESPONSE, + DOMAIN_2 to STATE_NO_RESPONSE, + )) + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_NONE, + DOMAIN_2 to DOMAIN_STATE_NONE, + )) + assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName) + + // Now remove the package because migrateState shouldn't use it either + map.remove(pkgName) + + service.migrateState(pkgBefore, pkgAfter) + + map[pkgName] = pkgAfter + + assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS)) + .isNotEqualTo(DomainVerificationManager.STATUS_OK) + + assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID)) + .isNotEqualTo(DomainVerificationManager.STATUS_OK) + + assertThat(service.getDomainVerificationInfo(pkgName)).isNull() + assertThat(service.getUserState(pkgName).hostToStateMap).isEmpty() + assertThat(service.queryValidVerificationPackageNames()).isEmpty() + } + + @Test + fun migratePackageAddDomain() { + val pkgName = PKG_ONE + val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, + listOf(DOMAIN_1, DOMAIN_2)) + val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, + listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3)) + + // Test 3 domains: + // 1 will be verified and preserved, 2 will be selected and preserved, + // 3 will be new and default + + val map = mutableMapOf<String, PackageSetting>() + val service = makeService { map[it] } + service.addPackage(pkgBefore) + + // Only insert the package after addPackage call to ensure the service doesn't access + // a live package inside the addPackage logic. It should only use the provided input. + map[pkgName] = pkgBefore + + service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS) + service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID) + + assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_SUCCESS, + DOMAIN_2 to STATE_NO_RESPONSE, + )) + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + )) + + // Now remove the package because migrateState shouldn't use it either + map.remove(pkgName) + + service.migrateState(pkgBefore, pkgAfter) + + map[pkgName] = pkgAfter + + assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_SUCCESS, + DOMAIN_2 to STATE_NO_RESPONSE, + DOMAIN_3 to STATE_NO_RESPONSE, + )) + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + DOMAIN_3 to DOMAIN_STATE_NONE, + )) + assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName) + } + + @Test + fun migratePackageAddAll() { + val pkgName = PKG_ONE + val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, emptyList()) + val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2)) + + val map = mutableMapOf<String, PackageSetting>() + val service = makeService { map[it] } + service.addPackage(pkgBefore) + + // Only insert the package after addPackage call to ensure the service doesn't access + // a live package inside the addPackage logic. It should only use the provided input. + map[pkgName] = pkgBefore + + assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS)) + .isNotEqualTo(DomainVerificationManager.STATUS_OK) + + assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID)) + .isNotEqualTo(DomainVerificationManager.STATUS_OK) + + assertThat(service.getDomainVerificationInfo(pkgName)).isNull() + assertThat(service.getUserState(pkgName).hostToStateMap).isEmpty() + assertThat(service.queryValidVerificationPackageNames()).isEmpty() + + // Now remove the package because migrateState shouldn't use it either + map.remove(pkgName) + + service.migrateState(pkgBefore, pkgAfter) + + map[pkgName] = pkgAfter + + assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_NO_RESPONSE, + DOMAIN_2 to STATE_NO_RESPONSE, + )) + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_NONE, + DOMAIN_2 to DOMAIN_STATE_NONE, + )) + assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName) + } + + private fun DomainVerificationService.getInfo(pkgName: String) = + getDomainVerificationInfo(pkgName) + .also { assertThat(it).isNotNull() }!! + + private fun DomainVerificationService.getUserState(pkgName: String) = + getDomainVerificationUserState(pkgName, USER_ID) + .also { assertThat(it).isNotNull() }!! + + private fun makeService(vararg pkgSettings: PackageSetting) = + makeService { pkgName -> pkgSettings.find { pkgName == it.getName()} } + + private fun makeService(pkgSettingFunction: (String) -> PackageSetting? = { null }) = + DomainVerificationService(mockThrowOnUnmocked { + // Assume the test has every permission necessary + whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString())) + whenever(checkPermission(anyString(), anyInt(), anyInt())) { + PackageManager.PERMISSION_GRANTED + } + }, mockThrowOnUnmocked { + whenever(linkedApps) { ArraySet<String>() } + }, mockThrowOnUnmocked { + whenever(isChangeEnabled(ArgumentMatchers.anyLong(), any())) { true } + }).apply { + setConnection(mockThrowOnUnmocked { + whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false } + whenever(doesUserExist(0)) { true } + whenever(doesUserExist(1)) { true } + whenever(scheduleWriteSettings()) + + // Need to provide an internal UID so some permission checks are ignored + whenever(callingUid) { Process.ROOT_UID } + whenever(callingUserId) { 0 } + + whenever(getPackageSettingLocked(anyString())) { + pkgSettingFunction(arguments[0] as String)!! + } + whenever(getPackageLocked(anyString())) { + pkgSettingFunction(arguments[0] as String)!!.getPkg() + } + }) + } + + private fun mockPkgSetting(pkgName: String, domainSetId: UUID, domains: List<String> = listOf( + DOMAIN_1, DOMAIN_2 + )) = mockThrowOnUnmocked<PackageSetting> { + val pkg = mockThrowOnUnmocked<AndroidPackage> { + whenever(packageName) { pkgName } + whenever(targetSdkVersion) { Build.VERSION_CODES.S } + + val activityList = listOf( + ParsedActivity().apply { + domains.forEach { + addIntent( + ParsedIntentInfo().apply { + autoVerify = true + addAction(Intent.ACTION_VIEW) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataScheme("http") + addDataScheme("https") + addDataPath("/sub", PatternMatcher.PATTERN_LITERAL) + addDataAuthority(it, null) + } + ) + } + }, + ) + + whenever(activities) { activityList } + } + + whenever(getPkg()) { pkg } + whenever(getName()) { pkgName } + whenever(this.domainSetId) { domainSetId } + whenever(getInstantApp(anyInt())) { false } + whenever(firstInstallTime) { 0L } + } +} diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt index 6597577cf14f..f8fda12f806d 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.verify.domain -import android.content.pm.verify.domain.DomainVerificationManager +import android.content.pm.verify.domain.DomainVerificationState import android.util.ArrayMap import android.util.TypedXmlPullParser import android.util.TypedXmlSerializer @@ -117,11 +117,11 @@ class DomainVerificationPersistenceTest { @Test fun readMalformed() { val stateZero = mockEmptyPkgState(0).apply { - stateMap["example.com"] = DomainVerificationManager.STATE_SUCCESS - stateMap["example.org"] = DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED + stateMap["example.com"] = DomainVerificationState.STATE_SUCCESS + stateMap["example.org"] = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED // A domain without a written state falls back to default - stateMap["missing-state.com"] = DomainVerificationManager.STATE_NO_RESPONSE + stateMap["missing-state.com"] = DomainVerificationState.STATE_NO_RESPONSE userStates[1] = DomainVerificationInternalUserState(1).apply { addHosts(setOf("example-user1.com", "example-user1.org")) @@ -159,9 +159,9 @@ class DomainVerificationPersistenceTest { > <state> <domain name="example.com" state="${ - DomainVerificationManager.STATE_SUCCESS}"/> + DomainVerificationState.STATE_SUCCESS}"/> <domain name="example.org" state="${ - DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED}"/> + DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED}"/> <not-domain name="not-domain.com" state="1"/> <domain name="missing-state.com"/> </state> diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt index 91e5beccee09..a9b77ea6d95b 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt @@ -21,20 +21,20 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager +import android.content.pm.verify.domain.DomainVerificationInfo import android.content.pm.verify.domain.DomainVerificationManager import android.content.pm.verify.domain.DomainVerificationRequest -import android.content.pm.verify.domain.DomainVerificationInfo import android.content.pm.verify.domain.DomainVerificationState import android.os.Bundle import android.os.UserHandle import android.util.ArraySet import com.android.server.DeviceIdleInternal +import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.verify.domain.DomainVerificationCollector import com.android.server.pm.verify.domain.DomainVerificationManagerInternal import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV2 -import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertThat @@ -106,20 +106,22 @@ class DomainVerificationProxyTest { when (val pkgName = arguments[0] as String) { TEST_PKG_NAME_TARGET_ONE -> DomainVerificationInfo( TEST_UUID_ONE, pkgName, mapOf( - "example1.com" to DomainVerificationManager.STATE_NO_RESPONSE, - "example2.com" to DomainVerificationManager.STATE_NO_RESPONSE + "example1.com" to DomainVerificationInfo.STATE_NO_RESPONSE, + "example2.com" to DomainVerificationInfo.STATE_NO_RESPONSE ) ) TEST_PKG_NAME_TARGET_TWO -> DomainVerificationInfo( TEST_UUID_TWO, pkgName, mapOf( - "example3.com" to DomainVerificationManager.STATE_NO_RESPONSE, - "example4.com" to DomainVerificationManager.STATE_NO_RESPONSE + "example3.com" to DomainVerificationInfo.STATE_NO_RESPONSE, + "example4.com" to DomainVerificationInfo.STATE_NO_RESPONSE ) ) else -> throw IllegalArgumentException("Unexpected package name $pkgName") } } - whenever(setDomainVerificationStatusInternal(anyInt(), any(), any(), anyInt())) + whenever(setDomainVerificationStatusInternal(anyInt(), any(), any(), anyInt())) { + DomainVerificationManager.STATUS_OK + } } collector = mockThrowOnUnmocked { whenever(collectValidAutoVerifyDomains(any())) { @@ -316,7 +318,7 @@ class DomainVerificationProxyTest { eq(TEST_CALLING_UID_ACCEPT), idCaptor.capture(), hostCaptor.capture(), - eq(DomainVerificationManager.STATE_SUCCESS) + eq(DomainVerificationState.STATE_SUCCESS) ) assertThat(idCaptor.allValues).containsExactly(TEST_UUID_ONE, TEST_UUID_TWO) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationServiceUtil.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationServiceUtil.kt new file mode 100644 index 000000000000..6859b113298d --- /dev/null +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationServiceUtil.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.test.verify.domain + +import android.annotation.UserIdInt +import com.android.server.pm.verify.domain.DomainVerificationService +import java.util.UUID + +fun DomainVerificationService.setStatus(domainSetId: UUID, domains: Set<String>, state: Int) = + setDomainVerificationStatus(domainSetId, domains.toMutableSet(), state) + +fun DomainVerificationService.setUserSelection( + domainSetId: UUID, + domains: Set<String>, + enabled: Boolean, + @UserIdInt userId: Int +) = setDomainVerificationUserSelection(domainSetId, domains.toMutableSet(), enabled, userId) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt index 0d8f275be09c..377bae15e2d5 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt @@ -20,28 +20,27 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.pm.PackageUserState -import android.content.pm.verify.domain.DomainVerificationManager import android.content.pm.parsing.component.ParsedActivity import android.content.pm.parsing.component.ParsedIntentInfo +import android.content.pm.verify.domain.DomainVerificationState import android.os.Build import android.os.Process import android.util.ArraySet import android.util.SparseArray import com.android.server.pm.PackageSetting +import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.verify.domain.DomainVerificationManagerInternal import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy -import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.spyThrowOnUnmocked import com.android.server.testutils.whenever import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized -import org.mockito.Mockito +import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyLong -import org.mockito.Mockito.any import org.mockito.Mockito.anyString import org.mockito.Mockito.eq import org.mockito.Mockito.verify @@ -129,13 +128,13 @@ class DomainVerificationSettingsMutationTest { setDomainVerificationStatus( TEST_UUID, setOf("example.com"), - DomainVerificationManager.STATE_SUCCESS + DomainVerificationState.STATE_SUCCESS ) }, service("setStatusInternalPackageName") { setDomainVerificationStatusInternal( TEST_PKG, - DomainVerificationManager.STATE_SUCCESS, + DomainVerificationState.STATE_SUCCESS, ArraySet(setOf("example.com")) ) }, @@ -144,7 +143,7 @@ class DomainVerificationSettingsMutationTest { TEST_UID, TEST_UUID, setOf("example.com"), - DomainVerificationManager.STATE_SUCCESS + DomainVerificationState.STATE_SUCCESS ) }, service("setLinkHandlingAllowedUserId") { @@ -266,5 +265,7 @@ class DomainVerificationSettingsMutationTest { // This doesn't check for visibility; that's done in the enforcer test whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false } + whenever(doesUserExist(0)) { true } + whenever(doesUserExist(10)) { true } } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt index 0576125748fb..44c1b8f3fbb9 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt @@ -21,6 +21,7 @@ import android.content.pm.PackageManager import android.content.pm.parsing.component.ParsedActivity import android.content.pm.parsing.component.ParsedIntentInfo import android.content.pm.verify.domain.DomainVerificationManager +import android.content.pm.verify.domain.DomainVerificationState import android.content.pm.verify.domain.DomainVerificationUserState import android.os.Build import android.os.PatternMatcher @@ -74,6 +75,8 @@ class DomainVerificationUserStateOverrideTest { }).apply { setConnection(mockThrowOnUnmocked { whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false } + whenever(doesUserExist(0)) { true } + whenever(doesUserExist(1)) { true } whenever(scheduleWriteSettings()) // Need to provide an internal UID so some permission checks are ignored @@ -154,19 +157,20 @@ class DomainVerificationUserStateOverrideTest { .containsExactly(PKG_TWO) } - @Test(expected = IllegalArgumentException::class) + @Test fun anotherPackageTakeoverFailure() { val service = makeService() // Verify 1 to give it a higher approval level service.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE), - DomainVerificationManager.STATE_SUCCESS) + DomainVerificationState.STATE_SUCCESS) assertThat(service.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_VERIFIED) assertThat(service.getOwnersForDomain(DOMAIN_ONE, USER_ID).map { it.packageName }) .containsExactly(PKG_ONE) // Attempt override by package 2 - service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true, USER_ID) + assertThat(service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true, + USER_ID)).isEqualTo(DomainVerificationManager.ERROR_UNABLE_TO_APPROVE) } private fun DomainVerificationService.stateFor(pkgName: String, host: String) = diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/mockingservicestests/src/com/android/server/pm/dex/DexManagerTests.java index ee0a16a70265..2e0cadf264cf 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -11,7 +11,7 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ package com.android.server.pm.dex; @@ -28,28 +28,34 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.os.BatteryManager; import android.os.Build; +import android.os.PowerManager; import android.os.UserHandle; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.server.pm.Installer; +import com.android.server.pm.PackageManagerService; import dalvik.system.DelegateLastClassLoader; import dalvik.system.PathClassLoader; import dalvik.system.VMRuntime; +import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; import org.mockito.quality.Strictness; import java.io.File; @@ -69,9 +75,15 @@ public class DexManagerTests { DelegateLastClassLoader.class.getName(); private static final String UNSUPPORTED_CLASS_LOADER_NAME = "unsupported.class_loader"; - @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); + private static final int TEST_BATTERY_LEVEL_CRITICAL = 10; + private static final int TEST_BATTERY_LEVEL_DEFAULT = 80; + + public StaticMockitoSession mMockitoSession; @Mock Installer mInstaller; @Mock IPackageManager mPM; + @Mock BatteryManager mMockBatteryManager; + @Mock PowerManager mMockPowerManager; + private final Object mInstallLock = new Object(); private DexManager mDexManager; @@ -117,7 +129,37 @@ public class DexManagerTests { mSystemServerJarUpdatedContext = new TestData("android", isa, mUser0, DELEGATE_LAST_CLASS_LOADER_NAME); - mDexManager = new DexManager(/*Context*/ null, mPM, /*PackageDexOptimizer*/ null, + // Initialize Static Mocking + + mMockitoSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .startMocking(); + + // Mock.... + + mMockBatteryManager = ExtendedMockito.mock(BatteryManager.class); + mMockPowerManager = ExtendedMockito.mock(PowerManager.class); + + setDefaultMockValues(); + + Resources mockResources = ExtendedMockito.mock(Resources.class); + ExtendedMockito.when(mockResources + .getInteger(com.android.internal.R.integer.config_criticalBatteryWarningLevel)) + .thenReturn(15); + + Context mockContext = ExtendedMockito.mock(Context.class); + ExtendedMockito.doReturn(mockResources) + .when(mockContext) + .getResources(); + ExtendedMockito.doReturn(mMockBatteryManager) + .when(mockContext) + .getSystemService(BatteryManager.class); + ExtendedMockito.doReturn(mMockPowerManager) + .when(mockContext) + .getSystemService(PowerManager.class); + + mDexManager = new DexManager(mockContext, mPM, /*PackageDexOptimizer*/ null, mInstaller, mInstallLock); // Foo and Bar are available to user0. @@ -128,6 +170,25 @@ public class DexManagerTests { mDexManager.load(existingPackages); } + @After + public void teardown() throws Exception { + mMockitoSession.finishMocking(); + } + + private void setDefaultMockValues() { + ExtendedMockito.doReturn(BatteryManager.BATTERY_STATUS_DISCHARGING) + .when(mMockBatteryManager) + .getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS); + + ExtendedMockito.doReturn(TEST_BATTERY_LEVEL_DEFAULT) + .when(mMockBatteryManager) + .getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); + + ExtendedMockito.doReturn(PowerManager.THERMAL_STATUS_NONE) + .when(mMockPowerManager) + .getCurrentThermalStatus(); + } + @Test public void testNotifyPrimaryUse() { // The main dex file and splits are re-loaded by the app. @@ -633,6 +694,114 @@ public class DexManagerTests { assertNoDclInfo(mSystemServerJarInvalid); } + @Test + public void testInstallScenarioToReasonDefault() { + assertEquals( + PackageManagerService.REASON_INSTALL, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_DEFAULT)); + + assertEquals( + PackageManagerService.REASON_INSTALL_FAST, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_FAST)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK_SECONDARY, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK_SECONDARY)); + } + + @Test + public void testInstallScenarioToReasonThermal() { + ExtendedMockito.doReturn(PowerManager.THERMAL_STATUS_SEVERE) + .when(mMockPowerManager) + .getCurrentThermalStatus(); + + assertEquals( + PackageManagerService.REASON_INSTALL, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_DEFAULT)); + + assertEquals( + PackageManagerService.REASON_INSTALL_FAST, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_FAST)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK_SECONDARY)); + } + + @Test + public void testInstallScenarioToReasonBatteryDischarging() { + ExtendedMockito.doReturn(TEST_BATTERY_LEVEL_CRITICAL) + .when(mMockBatteryManager) + .getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); + + assertEquals( + PackageManagerService.REASON_INSTALL, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_DEFAULT)); + + assertEquals( + PackageManagerService.REASON_INSTALL_FAST, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_FAST)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK_SECONDARY)); + } + + @Test + public void testInstallScenarioToReasonBatteryCharging() { + ExtendedMockito.doReturn(TEST_BATTERY_LEVEL_CRITICAL) + .when(mMockBatteryManager) + .getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); + + ExtendedMockito.doReturn(BatteryManager.BATTERY_STATUS_CHARGING) + .when(mMockBatteryManager) + .getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS); + + assertEquals( + PackageManagerService.REASON_INSTALL, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_DEFAULT)); + + assertEquals( + PackageManagerService.REASON_INSTALL_FAST, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_FAST)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK_SECONDARY, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK_SECONDARY)); + } + private void assertSecondaryUse(TestData testData, PackageUseInfo pui, List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId, String[] expectedContexts) { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/dex/OWNERS b/services/tests/mockingservicestests/src/com/android/server/pm/dex/OWNERS new file mode 100644 index 000000000000..5a4431ee8c89 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/dex/OWNERS @@ -0,0 +1,2 @@ +calin@google.com +ngeoffray@google.com diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/OWNERS b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/OWNERS new file mode 100644 index 000000000000..e95633abe79a --- /dev/null +++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/OWNERS @@ -0,0 +1 @@ +include /core/java/android/app/admin/OWNERS diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest2/OWNERS b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest2/OWNERS new file mode 100644 index 000000000000..e95633abe79a --- /dev/null +++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest2/OWNERS @@ -0,0 +1 @@ +include /core/java/android/app/admin/OWNERS diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest3/OWNERS b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest3/OWNERS new file mode 100644 index 000000000000..e95633abe79a --- /dev/null +++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest3/OWNERS @@ -0,0 +1 @@ +include /core/java/android/app/admin/OWNERS diff --git a/services/tests/servicestests/assets/OwnersTest/OWNERS b/services/tests/servicestests/assets/OwnersTest/OWNERS new file mode 100644 index 000000000000..e95633abe79a --- /dev/null +++ b/services/tests/servicestests/assets/OwnersTest/OWNERS @@ -0,0 +1 @@ +include /core/java/android/app/admin/OWNERS diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/OWNERS b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/OWNERS new file mode 100644 index 000000000000..e95633abe79a --- /dev/null +++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/OWNERS @@ -0,0 +1 @@ +include /core/java/android/app/admin/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java index c7e7c7861370..45f43e8b672f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java @@ -16,6 +16,8 @@ package com.android.server.accessibility; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + import static junit.framework.Assert.assertFalse; import static junit.framework.TestCase.assertNull; import static junit.framework.TestCase.assertTrue; @@ -30,6 +32,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -47,10 +50,13 @@ import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.testing.DexmakerShareClassLoaderRule; +import android.testing.TestableContext; import android.util.ArraySet; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityWindowInfo; +import com.android.internal.R; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -58,7 +64,9 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; /** @@ -72,9 +80,9 @@ public class AccessibilitySecurityPolicyTest { private static final int APP_UID = 10400; private static final int APP_PID = 2000; private static final int SYSTEM_PID = 558; - - private static final String PERMISSION = "test-permission"; - private static final String FUNCTION = "test-function-name"; + private static final int TEST_USER_ID = UserHandle.USER_SYSTEM; + private static final ComponentName TEST_COMPONENT_NAME = new ComponentName( + "com.android.server.accessibility", "AccessibilitySecurityPolicyTest"); private static final int[] ALWAYS_DISPATCH_EVENTS = { AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, @@ -108,29 +116,51 @@ public class AccessibilitySecurityPolicyTest { private AccessibilitySecurityPolicy mA11ySecurityPolicy; + @Rule + public final TestableContext mContext = new TestableContext( + getInstrumentation().getTargetContext(), null); + // To mock package-private class - @Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = + @Rule + public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = new DexmakerShareClassLoaderRule(); - @Mock private Context mMockContext; - @Mock private PackageManager mMockPackageManager; - @Mock private UserManager mMockUserManager; - @Mock private AppOpsManager mMockAppOpsManager; - @Mock private AccessibilityServiceConnection mMockA11yServiceConnection; - @Mock private AccessibilityWindowManager mMockA11yWindowManager; - @Mock private AppWidgetManagerInternal mMockAppWidgetManager; - @Mock private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager; + @Mock + private PackageManager mMockPackageManager; + @Mock + private UserManager mMockUserManager; + @Mock + private AppOpsManager mMockAppOpsManager; + @Mock + private AccessibilityServiceConnection mMockA11yServiceConnection; + @Mock + private AccessibilityWindowManager mMockA11yWindowManager; + @Mock + private AppWidgetManagerInternal mMockAppWidgetManager; + @Mock + private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager; + @Mock + private AccessibilityServiceInfo mMockA11yServiceInfo; + @Mock + private PolicyWarningUIController mPolicyWarningUIController; @Before public void setUp() { MockitoAnnotations.initMocks(this); - when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); - when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager); - when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mMockAppOpsManager); + mContext.setMockPackageManager(mMockPackageManager); + mContext.addMockSystemService(Context.USER_SERVICE, mMockUserManager); + mContext.addMockSystemService(Context.APP_OPS_SERVICE, mMockAppOpsManager); + mContext.getOrCreateTestableResources().addOverride( + R.dimen.accessibility_focus_highlight_stroke_width, 1); - mA11ySecurityPolicy = new AccessibilitySecurityPolicy(mMockContext, mMockA11yUserManager); + when(mMockA11yServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME); + when(mMockA11yServiceConnection.getServiceInfo()).thenReturn(mMockA11yServiceInfo); + + mA11ySecurityPolicy = new AccessibilitySecurityPolicy( + mPolicyWarningUIController, mContext, mMockA11yUserManager); mA11ySecurityPolicy.setAccessibilityWindowManager(mMockA11yWindowManager); mA11ySecurityPolicy.setAppWidgetManager(mMockAppWidgetManager); + mA11ySecurityPolicy.onSwitchUserLocked(TEST_USER_ID, new HashSet<>()); when(mMockA11yWindowManager.resolveParentWindowIdLocked(anyInt())).then(returnsFirstArg()); } @@ -141,7 +171,7 @@ public class AccessibilitySecurityPolicyTest { final AccessibilityEvent event = AccessibilityEvent.obtain(ALWAYS_DISPATCH_EVENTS[i]); assertTrue("Should dispatch [" + event + "]", mA11ySecurityPolicy.canDispatchAccessibilityEventLocked( - UserHandle.USER_SYSTEM, + TEST_USER_ID, event)); } } @@ -154,28 +184,28 @@ public class AccessibilitySecurityPolicyTest { event.setWindowId(invalidWindowId); assertFalse("Shouldn't dispatch [" + event + "]", mA11ySecurityPolicy.canDispatchAccessibilityEventLocked( - UserHandle.USER_SYSTEM, + TEST_USER_ID, event)); } } @Test public void canDispatchAccessibilityEvent_otherEvents_windowIdIsActive_returnTrue() { - when(mMockA11yWindowManager.getActiveWindowId(UserHandle.USER_SYSTEM)) + when(mMockA11yWindowManager.getActiveWindowId(TEST_USER_ID)) .thenReturn(WINDOWID); for (int i = 0; i < OTHER_EVENTS.length; i++) { final AccessibilityEvent event = AccessibilityEvent.obtain(OTHER_EVENTS[i]); event.setWindowId(WINDOWID); assertTrue("Should dispatch [" + event + "]", mA11ySecurityPolicy.canDispatchAccessibilityEventLocked( - UserHandle.USER_SYSTEM, + TEST_USER_ID, event)); } } @Test public void canDispatchAccessibilityEvent_otherEvents_windowIdExist_returnTrue() { - when(mMockA11yWindowManager.getActiveWindowId(UserHandle.USER_SYSTEM)) + when(mMockA11yWindowManager.getActiveWindowId(TEST_USER_ID)) .thenReturn(WINDOWID2); when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(WINDOWID)) .thenReturn(AccessibilityWindowInfo.obtain()); @@ -184,7 +214,7 @@ public class AccessibilitySecurityPolicyTest { event.setWindowId(WINDOWID); assertTrue("Should dispatch [" + event + "]", mA11ySecurityPolicy.canDispatchAccessibilityEventLocked( - UserHandle.USER_SYSTEM, + TEST_USER_ID, event)); } } @@ -192,24 +222,24 @@ public class AccessibilitySecurityPolicyTest { @Test public void resolveValidReportedPackage_nullPkgName_returnNull() { assertNull(mA11ySecurityPolicy.resolveValidReportedPackageLocked( - null, Process.SYSTEM_UID, UserHandle.USER_SYSTEM, SYSTEM_PID)); + null, Process.SYSTEM_UID, TEST_USER_ID, SYSTEM_PID)); } @Test public void resolveValidReportedPackage_uidIsSystem_returnPkgName() { assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked( - PACKAGE_NAME, Process.SYSTEM_UID, UserHandle.USER_SYSTEM, SYSTEM_PID), + PACKAGE_NAME, Process.SYSTEM_UID, TEST_USER_ID, SYSTEM_PID), PACKAGE_NAME); } @Test public void resolveValidReportedPackage_uidAndPkgNameMatched_returnPkgName() throws PackageManager.NameNotFoundException { - when(mMockPackageManager.getPackageUidAsUser(PACKAGE_NAME, UserHandle.USER_SYSTEM)) + when(mMockPackageManager.getPackageUidAsUser(PACKAGE_NAME, TEST_USER_ID)) .thenReturn(APP_UID); assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked( - PACKAGE_NAME, APP_UID, UserHandle.USER_SYSTEM, APP_PID), + PACKAGE_NAME, APP_UID, TEST_USER_ID, APP_PID), PACKAGE_NAME); } @@ -225,11 +255,11 @@ public class AccessibilitySecurityPolicyTest { when(mMockAppWidgetManager.getHostedWidgetPackages(widgetHostUid)) .thenReturn(widgetPackages); - when(mMockPackageManager.getPackageUidAsUser(hostPackageName, UserHandle.USER_SYSTEM)) + when(mMockPackageManager.getPackageUidAsUser(hostPackageName, TEST_USER_ID)) .thenReturn(widgetHostUid); assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked( - widgetPackageName, widgetHostUid, UserHandle.USER_SYSTEM, widgetHostPid), + widgetPackageName, widgetHostUid, TEST_USER_ID, widgetHostPid), widgetPackageName); } @@ -240,16 +270,16 @@ public class AccessibilitySecurityPolicyTest { final String[] uidPackages = {PACKAGE_NAME, PACKAGE_NAME2}; when(mMockPackageManager.getPackagesForUid(APP_UID)) .thenReturn(uidPackages); - when(mMockPackageManager.getPackageUidAsUser(invalidPackageName, UserHandle.USER_SYSTEM)) + when(mMockPackageManager.getPackageUidAsUser(invalidPackageName, TEST_USER_ID)) .thenThrow(PackageManager.NameNotFoundException.class); when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID)) .thenReturn(new ArraySet<>()); - when(mMockContext.checkPermission( - eq(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY), anyInt(), eq(APP_UID))) - .thenReturn(PackageManager.PERMISSION_DENIED); + mContext.getTestablePermissions().setPermission( + Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY, + PackageManager.PERMISSION_DENIED); assertEquals(PACKAGE_NAME, mA11ySecurityPolicy.resolveValidReportedPackageLocked( - invalidPackageName, APP_UID, UserHandle.USER_SYSTEM, APP_PID)); + invalidPackageName, APP_UID, TEST_USER_ID, APP_PID)); } @Test @@ -260,16 +290,16 @@ public class AccessibilitySecurityPolicyTest { final String[] uidPackages = {PACKAGE_NAME}; when(mMockPackageManager.getPackagesForUid(APP_UID)) .thenReturn(uidPackages); - when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, UserHandle.USER_SYSTEM)) + when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, TEST_USER_ID)) .thenReturn(wantedUid); when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID)) .thenReturn(new ArraySet<>()); - when(mMockContext.checkPermission( - eq(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY), anyInt(), eq(APP_UID))) - .thenReturn(PackageManager.PERMISSION_GRANTED); + mContext.getTestablePermissions().setPermission( + Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY, + PackageManager.PERMISSION_GRANTED); assertEquals(wantedPackageName, mA11ySecurityPolicy.resolveValidReportedPackageLocked( - wantedPackageName, APP_UID, UserHandle.USER_SYSTEM, APP_PID)); + wantedPackageName, APP_UID, TEST_USER_ID, APP_PID)); } @Test @@ -280,16 +310,16 @@ public class AccessibilitySecurityPolicyTest { final String[] uidPackages = {PACKAGE_NAME}; when(mMockPackageManager.getPackagesForUid(APP_UID)) .thenReturn(uidPackages); - when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, UserHandle.USER_SYSTEM)) + when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, TEST_USER_ID)) .thenReturn(wantedUid); when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID)) .thenReturn(new ArraySet<>()); - when(mMockContext.checkPermission( - eq(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY), anyInt(), eq(APP_UID))) - .thenReturn(PackageManager.PERMISSION_DENIED); + mContext.getTestablePermissions().setPermission( + Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY, + PackageManager.PERMISSION_DENIED); assertEquals(PACKAGE_NAME, mA11ySecurityPolicy.resolveValidReportedPackageLocked( - wantedPackageName, APP_UID, UserHandle.USER_SYSTEM, APP_PID)); + wantedPackageName, APP_UID, TEST_USER_ID, APP_PID)); } @Test @@ -301,7 +331,7 @@ public class AccessibilitySecurityPolicyTest { @Test public void computeValidReportedPackages_uidIsAppWidgetHost_returnTargetAndWidgetName() { final int widgetHostUid = APP_UID; - final String targetPackageName = PACKAGE_NAME; + final String targetPackageName = PACKAGE_NAME; final String widgetPackageName = PACKAGE_NAME2; final ArraySet<String> widgetPackages = new ArraySet<>(); widgetPackages.add(widgetPackageName); @@ -320,7 +350,7 @@ public class AccessibilitySecurityPolicyTest { when(mMockA11yServiceConnection.getCapabilities()) .thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT); - assertFalse(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(UserHandle.USER_SYSTEM, + assertFalse(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(TEST_USER_ID, mMockA11yServiceConnection, invalidWindowId)); } @@ -328,10 +358,10 @@ public class AccessibilitySecurityPolicyTest { public void canGetAccessibilityNodeInfo_hasCapAndWindowIsActive_returnTrue() { when(mMockA11yServiceConnection.getCapabilities()) .thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT); - when(mMockA11yWindowManager.getActiveWindowId(UserHandle.USER_SYSTEM)) + when(mMockA11yWindowManager.getActiveWindowId(TEST_USER_ID)) .thenReturn(WINDOWID); - assertTrue(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(UserHandle.USER_SYSTEM, + assertTrue(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(TEST_USER_ID, mMockA11yServiceConnection, WINDOWID)); } @@ -339,12 +369,12 @@ public class AccessibilitySecurityPolicyTest { public void canGetAccessibilityNodeInfo_hasCapAndWindowExist_returnTrue() { when(mMockA11yServiceConnection.getCapabilities()) .thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT); - when(mMockA11yWindowManager.getActiveWindowId(UserHandle.USER_SYSTEM)) + when(mMockA11yWindowManager.getActiveWindowId(TEST_USER_ID)) .thenReturn(WINDOWID2); when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(WINDOWID)) .thenReturn(AccessibilityWindowInfo.obtain()); - assertTrue(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(UserHandle.USER_SYSTEM, + assertTrue(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(TEST_USER_ID, mMockA11yServiceConnection, WINDOWID)); } @@ -464,8 +494,10 @@ public class AccessibilitySecurityPolicyTest { .thenReturn(currentUserId); doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked( callingUserId); - when(mMockContext.checkCallingPermission(any())) - .thenReturn(PackageManager.PERMISSION_DENIED); + mContext.getTestablePermissions().setPermission(Manifest.permission.INTERACT_ACROSS_USERS, + PackageManager.PERMISSION_DENIED); + mContext.getTestablePermissions().setPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, PackageManager.PERMISSION_DENIED); spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked( UserHandle.USER_CURRENT_OR_SELF); @@ -482,8 +514,8 @@ public class AccessibilitySecurityPolicyTest { .thenReturn(currentUserId); doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked( callingUserId); - when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS)) - .thenReturn(PackageManager.PERMISSION_GRANTED); + mContext.getTestablePermissions().setPermission(Manifest.permission.INTERACT_ACROSS_USERS, + PackageManager.PERMISSION_GRANTED); assertEquals(wantedUserId, spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(wantedUserId)); @@ -500,8 +532,8 @@ public class AccessibilitySecurityPolicyTest { .thenReturn(currentUserId); doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked( callingUserId); - when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)) - .thenReturn(PackageManager.PERMISSION_GRANTED); + mContext.getTestablePermissions().setPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, PackageManager.PERMISSION_GRANTED); assertEquals(wantedUserId, spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(wantedUserId)); @@ -518,10 +550,10 @@ public class AccessibilitySecurityPolicyTest { .thenReturn(currentUserId); doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked( callingUserId); - when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS)) - .thenReturn(PackageManager.PERMISSION_DENIED); - when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)) - .thenReturn(PackageManager.PERMISSION_DENIED); + mContext.getTestablePermissions().setPermission(Manifest.permission.INTERACT_ACROSS_USERS, + PackageManager.PERMISSION_DENIED); + mContext.getTestablePermissions().setPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, PackageManager.PERMISSION_DENIED); spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(wantedUserId); } @@ -562,4 +594,57 @@ public class AccessibilitySecurityPolicyTest { APP_UID, PACKAGE_NAME); } + @Test + public void onBoundServicesChanged_bindA11yCategoryService_noUIControllerAction() { + final ArrayList<AccessibilityServiceConnection> boundServices = new ArrayList<>(); + boundServices.add(mMockA11yServiceConnection); + when(mMockA11yServiceInfo.isAccessibilityTool()).thenReturn(true); + + mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, boundServices); + + verify(mPolicyWarningUIController, never()).onNonA11yCategoryServiceBound(anyInt(), any()); + } + + @Test + public void onBoundServicesChanged_unbindA11yCategoryService_noUIControllerAction() { + onBoundServicesChanged_bindA11yCategoryService_noUIControllerAction(); + + mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, new ArrayList<>()); + + verify(mPolicyWarningUIController, never()).onNonA11yCategoryServiceUnbound(anyInt(), + any()); + } + + @Test + public void onBoundServicesChanged_bindNonA11yCategoryService_activateUIControllerAction() { + final ArrayList<AccessibilityServiceConnection> boundServices = new ArrayList<>(); + boundServices.add(mMockA11yServiceConnection); + when(mMockA11yServiceInfo.isAccessibilityTool()).thenReturn(false); + + mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, boundServices); + + verify(mPolicyWarningUIController).onNonA11yCategoryServiceBound(eq(TEST_USER_ID), + eq(TEST_COMPONENT_NAME)); + } + + @Test + public void onBoundServicesChanged_unbindNonA11yCategoryService_activateUIControllerAction() { + onBoundServicesChanged_bindNonA11yCategoryService_activateUIControllerAction(); + + mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, new ArrayList<>()); + + verify(mPolicyWarningUIController).onNonA11yCategoryServiceUnbound(eq(TEST_USER_ID), + eq(TEST_COMPONENT_NAME)); + } + + @Test + public void onSwitchUser_differentUser_activateUIControllerAction() { + onBoundServicesChanged_bindNonA11yCategoryService_activateUIControllerAction(); + + mA11ySecurityPolicy.onSwitchUserLocked(2, new HashSet<>()); + + verify(mPolicyWarningUIController).onSwitchUserLocked(eq(2), eq(new HashSet<>())); + verify(mPolicyWarningUIController).onNonA11yCategoryServiceUnbound(eq(TEST_USER_ID), + eq(TEST_COMPONENT_NAME)); + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/PolicyWarningUIControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/PolicyWarningUIControllerTest.java new file mode 100644 index 000000000000..01a641f141ad --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/PolicyWarningUIControllerTest.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility; + +import static android.app.AlarmManager.RTC_WAKEUP; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_VIEW_AND_CONTROL_ACCESS; + +import static com.google.common.truth.Truth.assertThat; + +import static junit.framework.Assert.assertEquals; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.app.AlarmManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.StatusBarManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Bundle; +import android.os.UserHandle; +import android.provider.Settings; +import android.testing.TestableContext; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Tests for the {@link PolicyWarningUIController}. + */ +public class PolicyWarningUIControllerTest { + private static final int TEST_USER_ID = UserHandle.USER_SYSTEM; + private static final ComponentName TEST_COMPONENT_NAME = new ComponentName( + "com.android.server.accessibility", "PolicyWarningUIControllerTest"); + + private final List<AccessibilityServiceInfo> mEnabledServiceList = new ArrayList<>(); + + @Rule + public final A11yTestableContext mContext = new A11yTestableContext( + getInstrumentation().getTargetContext()); + @Mock + private AlarmManager mAlarmManager; + @Mock + private NotificationManager mNotificationManager; + @Mock + private StatusBarManager mStatusBarManager; + @Mock + private AccessibilityServiceInfo mMockA11yServiceInfo; + @Mock + private ResolveInfo mMockResolveInfo; + @Mock + private ServiceInfo mMockServiceInfo; + @Mock + private Context mSpyContext; + @Mock + private AccessibilitySecurityPolicy mAccessibilitySecurityPolicy; + + private PolicyWarningUIController mPolicyWarningUIController; + private FakeNotificationController mFakeNotificationController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext.addMockSystemService(AlarmManager.class, mAlarmManager); + mContext.addMockSystemService(NotificationManager.class, mNotificationManager); + mContext.addMockSystemService(StatusBarManager.class, mStatusBarManager); + mFakeNotificationController = new FakeNotificationController(mContext); + mPolicyWarningUIController = new PolicyWarningUIController( + getInstrumentation().getTargetContext().getMainThreadHandler(), mContext, + mFakeNotificationController); + mPolicyWarningUIController.setAccessibilityPolicyManager(mAccessibilitySecurityPolicy); + mPolicyWarningUIController.onSwitchUserLocked(TEST_USER_ID, new HashSet<>()); + mEnabledServiceList.clear(); + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES, + "", TEST_USER_ID); + } + + @Test + public void receiveActionSendNotification_isNonA11yCategoryService_sendNotification() { + mEnabledServiceList.add(mMockA11yServiceInfo); + mMockResolveInfo.serviceInfo = mMockServiceInfo; + when(mMockA11yServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo); + when(mMockA11yServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME); + when(mAccessibilitySecurityPolicy.isA11yCategoryService( + mMockA11yServiceInfo)).thenReturn(false); + + mFakeNotificationController.onReceive(mContext, createIntent(TEST_USER_ID, + PolicyWarningUIController.ACTION_SEND_NOTIFICATION, + TEST_COMPONENT_NAME.flattenToShortString())); + + verify(mNotificationManager).notify(eq(TEST_COMPONENT_NAME.flattenToShortString()), + eq(NOTE_A11Y_VIEW_AND_CONTROL_ACCESS), any( + Notification.class)); + } + + @Test + public void receiveActionA11ySettings_launchA11ySettingsAndDismissNotification() { + mFakeNotificationController.onReceive(mContext, + createIntent(TEST_USER_ID, PolicyWarningUIController.ACTION_A11Y_SETTINGS, + TEST_COMPONENT_NAME.flattenToShortString())); + + verifyLaunchA11ySettings(); + verify(mNotificationManager).cancel(TEST_COMPONENT_NAME.flattenToShortString(), + NOTE_A11Y_VIEW_AND_CONTROL_ACCESS); + assertNotifiedSettingsEqual(TEST_USER_ID, + TEST_COMPONENT_NAME.flattenToShortString()); + } + + @Test + public void receiveActionDismissNotification_addToNotifiedSettings() { + mFakeNotificationController.onReceive(mContext, createIntent(TEST_USER_ID, + PolicyWarningUIController.ACTION_DISMISS_NOTIFICATION, + TEST_COMPONENT_NAME.flattenToShortString())); + + assertNotifiedSettingsEqual(TEST_USER_ID, + TEST_COMPONENT_NAME.flattenToShortString()); + } + + @Test + public void onEnabledServicesChangedLocked_serviceDisabled_removedFromNotifiedSettings() { + final Set<ComponentName> enabledServices = new HashSet<>(); + enabledServices.add(TEST_COMPONENT_NAME); + mPolicyWarningUIController.onEnabledServicesChangedLocked(TEST_USER_ID, enabledServices); + getInstrumentation().waitForIdleSync(); + receiveActionDismissNotification_addToNotifiedSettings(); + + mPolicyWarningUIController.onEnabledServicesChangedLocked(TEST_USER_ID, new HashSet<>()); + getInstrumentation().waitForIdleSync(); + + assertNotifiedSettingsEqual(TEST_USER_ID, ""); + } + + @Test + public void onNonA11yCategoryServiceBound_setAlarm() { + mPolicyWarningUIController.onNonA11yCategoryServiceBound(TEST_USER_ID, TEST_COMPONENT_NAME); + getInstrumentation().waitForIdleSync(); + + verify(mAlarmManager).set(eq(RTC_WAKEUP), anyLong(), + eq(PolicyWarningUIController.createPendingIntent(mContext, TEST_USER_ID, + PolicyWarningUIController.ACTION_SEND_NOTIFICATION, + TEST_COMPONENT_NAME.flattenToShortString()))); + } + + @Test + public void onNonA11yCategoryServiceUnbound_cancelAlarm() { + mPolicyWarningUIController.onNonA11yCategoryServiceUnbound(TEST_USER_ID, + TEST_COMPONENT_NAME); + getInstrumentation().waitForIdleSync(); + + verify(mAlarmManager).cancel( + eq(PolicyWarningUIController.createPendingIntent(mContext, TEST_USER_ID, + PolicyWarningUIController.ACTION_SEND_NOTIFICATION, + TEST_COMPONENT_NAME.flattenToShortString()))); + } + + private void assertNotifiedSettingsEqual(int userId, String settingString) { + final String notifiedServicesSetting = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES, + userId); + assertEquals(settingString, notifiedServicesSetting); + } + + private Intent createIntent(int userId, String action, String serviceComponentName) { + final Intent intent = new Intent(action); + intent.setPackage(mContext.getPackageName()) + .setIdentifier(serviceComponentName) + .putExtra(Intent.EXTRA_USER_ID, userId); + return intent; + } + + private void verifyLaunchA11ySettings() { + final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); + final ArgumentCaptor<UserHandle> userHandleCaptor = ArgumentCaptor.forClass( + UserHandle.class); + verify(mSpyContext).startActivityAsUser(intentCaptor.capture(), + any(), userHandleCaptor.capture()); + assertThat(intentCaptor.getValue().getAction()).isEqualTo( + Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS); + assertThat(userHandleCaptor.getValue().getIdentifier()).isEqualTo(TEST_USER_ID); + verify(mStatusBarManager).collapsePanels(); + } + + private class A11yTestableContext extends TestableContext { + A11yTestableContext(Context base) { + super(base); + } + + @Override + public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) { + mSpyContext.startActivityAsUser(intent, options, user); + } + } + + private class FakeNotificationController extends + PolicyWarningUIController.NotificationController { + FakeNotificationController(Context context) { + super(context); + } + + @Override + protected List<AccessibilityServiceInfo> getEnabledServiceInfos() { + return mEnabledServiceList; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java index 8d54ead75761..73a2febf72aa 100644 --- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java @@ -17,8 +17,10 @@ package com.android.server.am; import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL; +import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_BT; import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU; import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_DISPLAY; +import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -39,6 +41,7 @@ import android.util.SparseArray; import androidx.test.InstrumentationRegistry; import com.android.internal.os.BatteryStatsImpl; +import com.android.internal.os.PowerProfile; import org.junit.Before; import org.junit.Test; @@ -61,7 +64,7 @@ public class BatteryExternalStatsWorkerTest { public void setUp() { final Context context = InstrumentationRegistry.getContext(); - mBatteryStatsImpl = new TestBatteryStatsImpl(); + mBatteryStatsImpl = new TestBatteryStatsImpl(context); mPowerStatsInternal = new TestPowerStatsInternal(); mBatteryExternalStatsWorker = new BatteryExternalStatsWorker(new TestInjector(context), mBatteryStatsImpl); @@ -72,13 +75,24 @@ public class BatteryExternalStatsWorkerTest { final int numCpuClusters = 4; final int numOther = 3; - final IntArray tempAllIds = new IntArray(); // Add some energy consumers used by BatteryExternalStatsWorker. + final IntArray tempAllIds = new IntArray(); + final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0, "display"); tempAllIds.add(displayId); mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345); + final int wifiId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.WIFI, 0, + "wifi"); + tempAllIds.add(wifiId); + mPowerStatsInternal.incrementEnergyConsumption(wifiId, 23456); + + final int btId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.BLUETOOTH, 0, + "bt"); + tempAllIds.add(btId); + mPowerStatsInternal.incrementEnergyConsumption(btId, 34567); + final int[] cpuClusterIds = new int[numCpuClusters]; for (int i = 0; i < numCpuClusters; i++) { cpuClusterIds[i] = mPowerStatsInternal.addEnergyConsumer( @@ -109,6 +123,18 @@ public class BatteryExternalStatsWorkerTest { assertEquals(1, displayResults.length); assertEquals(displayId, displayResults[0].id); + final EnergyConsumerResult[] wifiResults = + mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_WIFI).getNow(null); + // Results should only have the wifi energy consumer + assertEquals(1, wifiResults.length); + assertEquals(wifiId, wifiResults[0].id); + + final EnergyConsumerResult[] bluetoothResults = + mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_BT).getNow(null); + // Results should only have the bluetooth energy consumer + assertEquals(1, bluetoothResults.length); + assertEquals(btId, bluetoothResults[0].id); + final EnergyConsumerResult[] cpuResults = mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_CPU).getNow(null); // Results should only have the cpu cluster energy consumers @@ -148,6 +174,9 @@ public class BatteryExternalStatsWorkerTest { } public class TestBatteryStatsImpl extends BatteryStatsImpl { + public TestBatteryStatsImpl(Context context) { + mPowerProfile = new PowerProfile(context, true /* forTest */); + } } public class TestPowerStatsInternal extends PowerStatsInternal { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java index a38745f2a66e..d9af51f819c3 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java @@ -42,7 +42,6 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.os.FileUtils; -import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.security.keystore.recovery.KeyChainSnapshot; @@ -109,7 +108,7 @@ public class KeySyncTaskTest { private RecoverySnapshotStorage mRecoverySnapshotStorage; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; private File mDatabaseFile; - private AndroidKeyStoreSecretKey mWrappingKey; + private SecretKey mWrappingKey; private PlatformEncryptionKey mEncryptKey; private KeySyncTask mKeySyncTask; @@ -848,7 +847,7 @@ public class KeySyncTaskTest { return keyGenerator.generateKey(); } - private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception { + private SecretKey generateAndroidKeyStoreKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance( KEY_ALGORITHM, ANDROID_KEY_STORE_PROVIDER); @@ -857,7 +856,7 @@ public class KeySyncTaskTest { .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); - return (AndroidKeyStoreSecretKey) keyGenerator.generateKey(); + return keyGenerator.generateKey(); } private static byte[] utf8Bytes(String s) { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java index c295177814b0..64130266b2c4 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java @@ -23,7 +23,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import android.content.Context; -import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; @@ -45,6 +44,7 @@ import java.util.Random; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; @SmallTest @@ -77,7 +77,7 @@ public class RecoverableKeyGeneratorTest { mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); - AndroidKeyStoreSecretKey platformKey = generatePlatformKey(); + SecretKey platformKey = generatePlatformKey(); mPlatformKey = new PlatformEncryptionKey(TEST_GENERATION_ID, platformKey); mDecryptKey = new PlatformDecryptionKey(TEST_GENERATION_ID, platformKey); mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mRecoverableKeyStoreDb); @@ -168,7 +168,7 @@ public class RecoverableKeyGeneratorTest { assertArrayEquals(rawMaterial, unwrappedMaterial); } - private AndroidKeyStoreSecretKey generatePlatformKey() throws Exception { + private SecretKey generatePlatformKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance( KEY_ALGORITHM, ANDROID_KEY_STORE_PROVIDER); @@ -177,7 +177,7 @@ public class RecoverableKeyGeneratorTest { .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); - return (AndroidKeyStoreSecretKey) keyGenerator.generateKey(); + return keyGenerator.generateKey(); } private static byte[] randomBytes(int n) { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index b65e4877da50..a227cd3c6f5c 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -45,7 +45,6 @@ import android.content.Intent; import android.os.Binder; import android.os.ServiceSpecificException; import android.os.UserHandle; -import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.security.keystore.recovery.KeyChainProtectionParams; @@ -1311,7 +1310,7 @@ public class RecoverableKeyStoreManagerTest { mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); } - private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception { + private SecretKey generateAndroidKeyStoreKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance( KEY_ALGORITHM, ANDROID_KEY_STORE_PROVIDER); @@ -1320,6 +1319,6 @@ public class RecoverableKeyStoreManagerTest { .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); - return (AndroidKeyStoreSecretKey) keyGenerator.generateKey(); + return keyGenerator.generateKey(); } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java index 9813ab74721e..60052f7114b3 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java @@ -21,7 +21,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.util.Pair; @@ -117,7 +116,7 @@ public class WrappedKeyTest { @Test public void decryptWrappedKeys_decryptsWrappedKeys_nullMetadata() throws Exception { String alias = "karlin"; - AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey(); + SecretKey platformKey = generateAndroidKeyStoreKey(); SecretKey appKey = generateKey(); WrappedKey wrappedKey = WrappedKey.fromSecretKey( new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey, NULL_METADATA); @@ -136,7 +135,7 @@ public class WrappedKeyTest { @Test public void decryptWrappedKeys_decryptsWrappedKeys_nonNullMetadata() throws Exception { String alias = "karlin"; - AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey(); + SecretKey platformKey = generateAndroidKeyStoreKey(); SecretKey appKey = generateKey(); WrappedKey wrappedKey = WrappedKey.fromSecretKey( new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey, NON_NULL_METADATA); @@ -155,7 +154,7 @@ public class WrappedKeyTest { @Test public void decryptWrappedKeys_doesNotDieIfSomeKeysAreUnwrappable() throws Exception { String alias = "karlin"; - AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey(); + SecretKey platformKey = generateAndroidKeyStoreKey(); SecretKey appKey = generateKey(); WrappedKey wrappedKey = WrappedKey.fromSecretKey( new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey, NULL_METADATA); @@ -171,7 +170,7 @@ public class WrappedKeyTest { @Test public void decryptWrappedKeys_throwsIfPlatformKeyGenerationIdDoesNotMatch() throws Exception { - AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey(); + SecretKey platformKey = generateAndroidKeyStoreKey(); WrappedKey wrappedKey = WrappedKey.fromSecretKey( new PlatformEncryptionKey(GENERATION_ID, platformKey), generateKey(), /*metadata=*/ null); @@ -197,7 +196,7 @@ public class WrappedKeyTest { return keyGenerator.generateKey(); } - private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception { + private SecretKey generateAndroidKeyStoreKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance( KEY_ALGORITHM, ANDROID_KEY_STORE_PROVIDER); @@ -207,6 +206,6 @@ public class WrappedKeyTest { .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); - return (AndroidKeyStoreSecretKey) keyGenerator.generateKey(); + return keyGenerator.generateKey(); } } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java index 3ab34484ce25..e605d755183f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java @@ -106,7 +106,8 @@ public final class ArtStatsLogUtilsTest { RESULT_CODE); // Assert - verifyWrites(ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__PROFILE_AND_VDEX); + verifyWrites(ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE_AND_VDEX); } finally { deleteSliently(dexMetadataPath); deleteSliently(apk); @@ -135,7 +136,8 @@ public final class ArtStatsLogUtilsTest { RESULT_CODE); // Assert - verifyWrites(ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__PROFILE); + verifyWrites(ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE); } finally { deleteSliently(dexMetadataPath); deleteSliently(apk); @@ -164,7 +166,8 @@ public final class ArtStatsLogUtilsTest { RESULT_CODE); // Assert - verifyWrites(ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__VDEX); + verifyWrites(ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_VDEX); } finally { deleteSliently(dexMetadataPath); deleteSliently(apk); @@ -191,7 +194,8 @@ public final class ArtStatsLogUtilsTest { RESULT_CODE); // Assert - verifyWrites(ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__NONE_DEX_METADATA); + verifyWrites(ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_NONE); } finally { deleteSliently(apk); } @@ -219,7 +223,8 @@ public final class ArtStatsLogUtilsTest { RESULT_CODE); // Assert - verifyWrites(ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__UNKNOWN_DEX_METADATA); + verifyWrites(ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN); } finally { deleteSliently(dexMetadataPath); deleteSliently(apk); diff --git a/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java new file mode 100644 index 000000000000..d4222e6e0b63 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 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.timedetector; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.time.Capabilities; +import android.app.time.TimeCapabilities; +import android.app.time.TimeCapabilitiesAndConfig; +import android.app.time.TimeConfiguration; +import android.os.UserHandle; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class ConfigurationInternalTest { + + @Test + public void capabilitiesAndConfig() { + int userId = 112233; + ConfigurationInternal configurationInternal = new ConfigurationInternal.Builder(userId) + .setAutoDetectionEnabled(true) + .setUserConfigAllowed(true) + .build(); + + TimeCapabilities timeCapabilities = new TimeCapabilities.Builder(UserHandle.of(userId)) + .setConfigureAutoTimeDetectionEnabledCapability(Capabilities.CAPABILITY_POSSESSED) + .setSuggestTimeManuallyCapability(Capabilities.CAPABILITY_POSSESSED) + .build(); + TimeConfiguration timeConfiguration = new TimeConfiguration.Builder() + .setAutoDetectionEnabled(true) + .build(); + TimeCapabilitiesAndConfig expected = + new TimeCapabilitiesAndConfig(timeCapabilities, timeConfiguration); + + assertThat(configurationInternal.capabilitiesAndConfig()).isEqualTo(expected); + } + +} diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index bbf11fd557a3..106827078290 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -328,6 +328,11 @@ public class TimeDetectorServiceTest { } @Override + public ConfigurationInternal getConfigurationInternal(int userId) { + throw new UnsupportedOperationException(); + } + + @Override public void handleAutoTimeConfigChanged() { mHandleAutoTimeDetectionChangedCalled = true; } diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java index f7a498bc9d73..095703e6b939 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java @@ -1158,6 +1158,11 @@ public class TimeDetectorStrategyImplTest { } @Override + public ConfigurationInternal configurationInternal(int userId) { + throw new UnsupportedOperationException(); + } + + @Override public void acquireWakeLock() { if (mWakeLockAcquired) { fail("Wake lock already acquired"); diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java index 00369829db56..aa46e7ef658f 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java @@ -16,10 +16,10 @@ package com.android.server.timezonedetector; -import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED; -import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE; -import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED; -import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED; +import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED; +import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE; +import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED; +import static android.app.time.Capabilities.CAPABILITY_POSSESSED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java index 8874e0afd716..72e1e33491ff 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerService.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java @@ -27,6 +27,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; @@ -203,6 +204,28 @@ public final class TranslationManagerService } } + @Override + public void registerUiTranslationStateCallback(IRemoteCallback callback, int userId) { + TranslationManagerServiceImpl service; + synchronized (mLock) { + service = getServiceForUserLocked(userId); + } + if (service != null) { + service.registerUiTranslationStateCallback(callback, Binder.getCallingUid()); + } + } + + @Override + public void unregisterUiTranslationStateCallback(IRemoteCallback callback, int userId) { + TranslationManagerServiceImpl service; + synchronized (mLock) { + service = getServiceForUserLocked(userId); + } + if (service != null) { + service.unregisterUiTranslationStateCallback(callback); + } + } + /** * Dump the service state into the given stream. You run "adb shell dumpsys translation". */ diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java index ab6ac12c90fa..1ca07cb8d928 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java @@ -17,17 +17,24 @@ package com.android.server.translation; import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_SUCCESS; +import static android.view.translation.UiTranslationManager.EXTRA_SOURCE_LOCALE; +import static android.view.translation.UiTranslationManager.EXTRA_STATE; +import static android.view.translation.UiTranslationManager.EXTRA_TARGET_LOCALE; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.os.Bundle; import android.os.IBinder; +import android.os.IRemoteCallback; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.service.translation.TranslationServiceInfo; import android.util.Slog; import android.view.autofill.AutofillId; +import android.view.inputmethod.InputMethodInfo; import android.view.translation.TranslationSpec; import android.view.translation.UiTranslationManager.UiTranslationState; @@ -36,6 +43,7 @@ import com.android.internal.os.IResultReceiver; import com.android.internal.util.SyncResultReceiver; import com.android.server.LocalServices; import com.android.server.infra.AbstractPerUserSystemService; +import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens; @@ -174,5 +182,50 @@ final class TranslationManagerServiceImpl extends } catch (RemoteException e) { Slog.w(TAG, "Update UiTranslationState fail: " + e); } + invokeCallbacks(state, sourceSpec, destSpec); } + + private void invokeCallbacks( + int state, TranslationSpec sourceSpec, TranslationSpec targetSpec) { + Bundle res = new Bundle(); + res.putInt(EXTRA_STATE, state); + // TODO(177500482): Store the locale pair so it can be sent for RESUME events. + if (sourceSpec != null) { + res.putString(EXTRA_SOURCE_LOCALE, sourceSpec.getLanguage()); + res.putString(EXTRA_TARGET_LOCALE, targetSpec.getLanguage()); + } + // TODO(177500482): Only support the *current* Input Method. + List<InputMethodInfo> enabledInputMethods = + LocalServices.getService(InputMethodManagerInternal.class) + .getEnabledInputMethodListAsUser(mUserId); + mCallbacks.broadcast((callback, uid) -> { + // Code here is non-optimal since it's temporary.. + boolean isIme = false; + for (InputMethodInfo inputMethod : enabledInputMethods) { + if ((int) uid == inputMethod.getServiceInfo().applicationInfo.uid) { + isIme = true; + } + } + // TODO(177500482): Invoke it for the application being translated too. + if (!isIme) { + return; + } + try { + callback.sendResult(res); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke UiTranslationStateCallback: " + e); + } + }); + } + + public void registerUiTranslationStateCallback(IRemoteCallback callback, int sourceUid) { + mCallbacks.register(callback, sourceUid); + // TODO(177500482): trigger the callback here if we're already translating the UI. + } + + public void unregisterUiTranslationStateCallback(IRemoteCallback callback) { + mCallbacks.unregister(callback); + } + + private final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>(); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 2626bfdc3d19..80d4f8fd0c2f 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -985,6 +985,7 @@ public class VoiceInteractionManagerService extends SystemService { @Override public void setHotwordDetectionServiceConfig(@Nullable Bundle options, @Nullable SharedMemory sharedMemory) { + enforceCallingPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION); synchronized (this) { enforceIsCurrentVoiceInteractionService(); diff --git a/telecomm/java/android/telecom/CallDiagnosticService.java b/telecomm/java/android/telecom/CallDiagnosticService.java index 5fb6b3381360..f5357b19c4de 100644 --- a/telecomm/java/android/telecom/CallDiagnosticService.java +++ b/telecomm/java/android/telecom/CallDiagnosticService.java @@ -27,8 +27,6 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; import android.os.RemoteException; -import android.telephony.Annotation; -import android.telephony.ims.ImsReasonInfo; import android.util.ArrayMap; import com.android.internal.telecom.ICallDiagnosticService; @@ -59,7 +57,7 @@ import java.util.concurrent.Executor; * </pre> * <p> * <h2>Threading Model</h2> - * By default, all incoming IPC from Telecom in this service and in the {@link DiagnosticCall} + * By default, all incoming IPC from Telecom in this service and in the {@link CallDiagnostics} * instances will take place on the main thread. You can override {@link #getExecutor()} in your * implementation to provide your own {@link Executor}. * @hide @@ -116,26 +114,28 @@ public abstract class CallDiagnosticService extends Service { } /** - * Listens to events raised by a {@link DiagnosticCall}. + * Listens to events raised by a {@link CallDiagnostics}. */ - private android.telecom.DiagnosticCall.Listener mDiagnosticCallListener = - new android.telecom.DiagnosticCall.Listener() { + private CallDiagnostics.Listener mDiagnosticCallListener = + new CallDiagnostics.Listener() { @Override - public void onSendDeviceToDeviceMessage(DiagnosticCall diagnosticCall, - @DiagnosticCall.MessageType int message, int value) { - handleSendDeviceToDeviceMessage(diagnosticCall, message, value); + public void onSendDeviceToDeviceMessage(CallDiagnostics callDiagnostics, + @CallDiagnostics.MessageType int message, int value) { + handleSendDeviceToDeviceMessage(callDiagnostics, message, value); } @Override - public void onDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId, + public void onDisplayDiagnosticMessage(CallDiagnostics callDiagnostics, + int messageId, CharSequence message) { - handleDisplayDiagnosticMessage(diagnosticCall, messageId, message); + handleDisplayDiagnosticMessage(callDiagnostics, messageId, message); } @Override - public void onClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) { - handleClearDiagnosticMessage(diagnosticCall, messageId); + public void onClearDiagnosticMessage(CallDiagnostics callDiagnostics, + int messageId) { + handleClearDiagnosticMessage(callDiagnostics, messageId); } }; @@ -149,7 +149,7 @@ public abstract class CallDiagnosticService extends Service { * Map which tracks the Telecom calls received from the Telecom stack. */ private final Map<String, Call.Details> mCallByTelecomCallId = new ArrayMap<>(); - private final Map<String, DiagnosticCall> mDiagnosticCallByTelecomCallId = new ArrayMap<>(); + private final Map<String, CallDiagnostics> mDiagnosticCallByTelecomCallId = new ArrayMap<>(); private final Object mLock = new Object(); private ICallDiagnosticServiceAdapter mAdapter; @@ -177,7 +177,7 @@ public abstract class CallDiagnosticService extends Service { * executor you want to use for incoming IPC. * * @return the {@link Executor} to use for incoming IPC from Telecom to - * {@link CallDiagnosticService} and {@link DiagnosticCall}. + * {@link CallDiagnosticService} and {@link CallDiagnostics}. */ @SuppressLint("OnNameExpected") @NonNull public Executor getExecutor() { @@ -188,30 +188,30 @@ public abstract class CallDiagnosticService extends Service { * Telecom calls this method on the {@link CallDiagnosticService} with details about a new call * which was added to Telecom. * <p> - * The {@link CallDiagnosticService} returns an implementation of {@link DiagnosticCall} to be + * The {@link CallDiagnosticService} returns an implementation of {@link CallDiagnostics} to be * used for the lifespan of this call. * <p> * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see * {@link CallDiagnosticService#getExecutor()} for more information. * * @param call The details of the new call. - * @return An instance of {@link DiagnosticCall} which the {@link CallDiagnosticService} + * @return An instance of {@link CallDiagnostics} which the {@link CallDiagnosticService} * provides to be used for the lifespan of the call. - * @throws IllegalArgumentException if a {@code null} {@link DiagnosticCall} is returned. + * @throws IllegalArgumentException if a {@code null} {@link CallDiagnostics} is returned. */ - public abstract @NonNull DiagnosticCall onInitializeDiagnosticCall(@NonNull + public abstract @NonNull CallDiagnostics onInitializeCallDiagnostics(@NonNull android.telecom.Call.Details call); /** - * Telecom calls this method when a previous created {@link DiagnosticCall} is no longer needed. - * This happens when Telecom is no longer tracking the call in question. + * Telecom calls this method when a previous created {@link CallDiagnostics} is no longer + * needed. This happens when Telecom is no longer tracking the call in question. * <p> * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see * {@link CallDiagnosticService#getExecutor()} for more information. * * @param call The diagnostic call which is no longer tracked by Telecom. */ - public abstract void onRemoveDiagnosticCall(@NonNull DiagnosticCall call); + public abstract void onRemoveCallDiagnostics(@NonNull CallDiagnostics call); /** * Telecom calls this method when the audio routing or available audio route information @@ -260,35 +260,35 @@ public abstract class CallDiagnosticService extends Service { } getExecutor().execute(() -> { - DiagnosticCall diagnosticCall = onInitializeDiagnosticCall(newCallDetails); - if (diagnosticCall == null) { + CallDiagnostics callDiagnostics = onInitializeCallDiagnostics(newCallDetails); + if (callDiagnostics == null) { throw new IllegalArgumentException( "A valid DiagnosticCall instance was not provided."); } synchronized (mLock) { - diagnosticCall.setListener(mDiagnosticCallListener); - diagnosticCall.setCallId(telecomCallId); - mDiagnosticCallByTelecomCallId.put(telecomCallId, diagnosticCall); + callDiagnostics.setListener(mDiagnosticCallListener); + callDiagnostics.setCallId(telecomCallId); + mDiagnosticCallByTelecomCallId.put(telecomCallId, callDiagnostics); } }); } /** * Handles an update to {@link Call.Details} notified by Telecom. - * Caches the call details and notifies the {@link DiagnosticCall} of the change via - * {@link DiagnosticCall#onCallDetailsChanged(Call.Details)}. + * Caches the call details and notifies the {@link CallDiagnostics} of the change via + * {@link CallDiagnostics#onCallDetailsChanged(Call.Details)}. * @param parcelableCall the new parceled call details from Telecom. */ private void handleCallUpdated(@NonNull ParcelableCall parcelableCall) { String telecomCallId = parcelableCall.getId(); Log.i(this, "handleCallUpdated: callId=%s - updated", telecomCallId); Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall); - DiagnosticCall diagnosticCall; + CallDiagnostics callDiagnostics; synchronized (mLock) { - diagnosticCall = mDiagnosticCallByTelecomCallId.get(telecomCallId); + callDiagnostics = mDiagnosticCallByTelecomCallId.get(telecomCallId); mCallByTelecomCallId.put(telecomCallId, newCallDetails); } - getExecutor().execute(() -> diagnosticCall.handleCallUpdated(newCallDetails)); + getExecutor().execute(() -> callDiagnostics.handleCallUpdated(newCallDetails)); } /** @@ -302,37 +302,37 @@ public abstract class CallDiagnosticService extends Service { mCallByTelecomCallId.remove(telecomCallId); } - DiagnosticCall diagnosticCall; + CallDiagnostics callDiagnostics; synchronized (mLock) { if (mDiagnosticCallByTelecomCallId.containsKey(telecomCallId)) { - diagnosticCall = mDiagnosticCallByTelecomCallId.remove(telecomCallId); + callDiagnostics = mDiagnosticCallByTelecomCallId.remove(telecomCallId); } else { - diagnosticCall = null; + callDiagnostics = null; } } // Inform the service of the removed call. - if (diagnosticCall != null) { - getExecutor().execute(() -> onRemoveDiagnosticCall(diagnosticCall)); + if (callDiagnostics != null) { + getExecutor().execute(() -> onRemoveCallDiagnostics(callDiagnostics)); } } /** * Handles an incoming device to device message received from Telecom. Notifies the - * {@link DiagnosticCall} via {@link DiagnosticCall#onReceiveDeviceToDeviceMessage(int, int)}. + * {@link CallDiagnostics} via {@link CallDiagnostics#onReceiveDeviceToDeviceMessage(int, int)}. * @param callId * @param message * @param value */ private void handleReceivedD2DMessage(@NonNull String callId, int message, int value) { Log.i(this, "handleReceivedD2DMessage: callId=%s, msg=%d/%d", callId, message, value); - DiagnosticCall diagnosticCall; + CallDiagnostics callDiagnostics; synchronized (mLock) { - diagnosticCall = mDiagnosticCallByTelecomCallId.get(callId); + callDiagnostics = mDiagnosticCallByTelecomCallId.get(callId); } - if (diagnosticCall != null) { + if (callDiagnostics != null) { getExecutor().execute( - () -> diagnosticCall.onReceiveDeviceToDeviceMessage(message, value)); + () -> callDiagnostics.onReceiveDeviceToDeviceMessage(message, value)); } } @@ -345,12 +345,12 @@ public abstract class CallDiagnosticService extends Service { private void handleCallDisconnected(@NonNull String callId, @NonNull DisconnectCause disconnectCause) { Log.i(this, "handleCallDisconnected: call=%s; cause=%s", callId, disconnectCause); - DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(callId); + CallDiagnostics callDiagnostics = mDiagnosticCallByTelecomCallId.get(callId); CharSequence message; if (disconnectCause.getImsReasonInfo() != null) { - message = diagnosticCall.onCallDisconnected(disconnectCause.getImsReasonInfo()); + message = callDiagnostics.onCallDisconnected(disconnectCause.getImsReasonInfo()); } else { - message = diagnosticCall.onCallDisconnected( + message = callDiagnostics.onCallDisconnected( disconnectCause.getTelephonyDisconnectCause(), disconnectCause.getTelephonyPreciseDisconnectCause()); } @@ -375,15 +375,15 @@ public abstract class CallDiagnosticService extends Service { } /** - * Handles a request from a {@link DiagnosticCall} to send a device to device message (received - * via {@link DiagnosticCall#sendDeviceToDeviceMessage(int, int)}. - * @param diagnosticCall + * Handles a request from a {@link CallDiagnostics} to send a device to device message (received + * via {@link CallDiagnostics#sendDeviceToDeviceMessage(int, int)}. + * @param callDiagnostics * @param message * @param value */ - private void handleSendDeviceToDeviceMessage(@NonNull DiagnosticCall diagnosticCall, + private void handleSendDeviceToDeviceMessage(@NonNull CallDiagnostics callDiagnostics, int message, int value) { - String callId = diagnosticCall.getCallId(); + String callId = callDiagnostics.getCallId(); try { mAdapter.sendDeviceToDeviceMessage(callId, message, value); Log.i(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d", callId, message, @@ -395,15 +395,15 @@ public abstract class CallDiagnosticService extends Service { } /** - * Handles a request from a {@link DiagnosticCall} to display an in-call diagnostic message. - * Originates from {@link DiagnosticCall#displayDiagnosticMessage(int, CharSequence)}. - * @param diagnosticCall + * Handles a request from a {@link CallDiagnostics} to display an in-call diagnostic message. + * Originates from {@link CallDiagnostics#displayDiagnosticMessage(int, CharSequence)}. + * @param callDiagnostics * @param messageId * @param message */ - private void handleDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId, + private void handleDisplayDiagnosticMessage(CallDiagnostics callDiagnostics, int messageId, CharSequence message) { - String callId = diagnosticCall.getCallId(); + String callId = callDiagnostics.getCallId(); try { mAdapter.displayDiagnosticMessage(callId, messageId, message); Log.i(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s", callId, messageId, @@ -415,14 +415,14 @@ public abstract class CallDiagnosticService extends Service { } /** - * Handles a request from a {@link DiagnosticCall} to clear a previously shown diagnostic + * Handles a request from a {@link CallDiagnostics} to clear a previously shown diagnostic * message. - * Originates from {@link DiagnosticCall#clearDiagnosticMessage(int)}. - * @param diagnosticCall + * Originates from {@link CallDiagnostics#clearDiagnosticMessage(int)}. + * @param callDiagnostics * @param messageId */ - private void handleClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) { - String callId = diagnosticCall.getCallId(); + private void handleClearDiagnosticMessage(CallDiagnostics callDiagnostics, int messageId) { + String callId = callDiagnostics.getCallId(); try { mAdapter.clearDiagnosticMessage(callId, messageId); Log.i(this, "handleClearDiagnosticMessage: call=%s; msg=%d", callId, messageId); diff --git a/telecomm/java/android/telecom/CallDiagnostics.java b/telecomm/java/android/telecom/CallDiagnostics.java new file mode 100644 index 000000000000..3356431f17b1 --- /dev/null +++ b/telecomm/java/android/telecom/CallDiagnostics.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2021 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.telecom; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.telephony.Annotation; +import android.telephony.CallQuality; +import android.telephony.ims.ImsReasonInfo; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + +/** + * {@link CallDiagnostics} provides a way for a {@link CallDiagnosticService} to receive diagnostic + * information about a mobile call on the device. A {@link CallDiagnostics} instance is similar to + * a {@link Call}, however it does not expose call control capabilities and exposes extra diagnostic + * and messaging capabilities not present on a {@link Call}. The {@link CallDiagnosticService} + * creates a {@link CallDiagnostics} for each {@link Call} on the device. This means that for each + * in progress call on the device, the {@link CallDiagnosticService} will create an instance of + * {@link CallDiagnostics}. + * <p> + * The {@link CallDiagnosticService} can generate mid-call diagnostic messages using the + * {@link #displayDiagnosticMessage(int, CharSequence)} API which provides the user with valuable + * information about conditions impacting their call and corrective actions. For example, if the + * {@link CallDiagnosticService} determines that conditions on the call are degrading, it can inform + * the user that the call may soon drop and that they can try using a different calling method + * (e.g. VOIP or WIFI). + * <h2>Threading Model</h2> + * All incoming IPC from Telecom in this class will use the same {@link Executor} as the + * {@link CallDiagnosticService}. See {@link CallDiagnosticService#setExecutor(Executor)} for more + * information. + * @hide + */ +@SystemApi +public abstract class CallDiagnostics { + + /** + * @hide + */ + public interface Listener { + /** + * Used to inform the {@link CallDiagnosticService} of a request to send a D2d message + * @param callDiagnostics the call the message is from. + * @param message the message type + * @param value the message value + */ + void onSendDeviceToDeviceMessage(CallDiagnostics callDiagnostics, int message, int value); + + /** + * Used to inform the {@link CallDiagnosticService} of a request to display a diagnostic + * message. + * @param callDiagnostics the call the message pertains to. + * @param messageId an identifier for the message. + * @param message the message to display. + */ + void onDisplayDiagnosticMessage(CallDiagnostics callDiagnostics, int messageId, + CharSequence message); + + /** + * Used to inform the {@link CallDiagnosticService} that a previously shown message is no + * longer pertinent. + * @param callDiagnostics the call the message pertains to. + * @param messageId the ID of the previously posted message. + */ + void onClearDiagnosticMessage(CallDiagnostics callDiagnostics, int messageId); + } + + /** + * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via + * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the radio access type + * used for the current call. The call network type communicated here is an intentional + * simplification of the {@link android.telephony.TelephonyManager#getNetworkType(int)} which + * removes some of the resolution inherent in those values; the + * {@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE_CA} value, for example is + * collapsed into the {@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE} value for + * efficiency of transport. For a discussion on the necessity of this simplification, see + * {@link #sendDeviceToDeviceMessage(int, int)}. + * <p> + * Valid values are below: + * <UL> + * <LI>{@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE}</LI> + * <LI>{@link android.telephony.TelephonyManager#NETWORK_TYPE_IWLAN}</LI> + * <LI>{@link android.telephony.TelephonyManager#NETWORK_TYPE_NR}</LI> + * </UL> + */ + public static final int MESSAGE_CALL_NETWORK_TYPE = 1; + + /** + * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via + * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the call audio codec + * used for the current call. + * <p> + * The audio codec communicated here is an intentional simplification of the + * {@link Connection#EXTRA_AUDIO_CODEC} for a call and focuses on communicating the most common + * variants of these audio codecs. Other variants of these codecs are reported as the next + * closest variant. For example, the {@link Connection#AUDIO_CODEC_EVS_FB} full band codec + * is reported via device to device communication as {@link Connection#AUDIO_CODEC_EVS_WB}. + * For a discussion on the necessity of this simplification, see + * {@link #sendDeviceToDeviceMessage(int, int)}. + * <p> + * Valid values: + * <UL> + * <LI>{@link Connection#AUDIO_CODEC_EVS_WB}</LI> + * <LI>{@link Connection#AUDIO_CODEC_AMR_WB}</LI> + * <LI>{@link Connection#AUDIO_CODEC_AMR}</LI> + * </UL> + */ + public static final int MESSAGE_CALL_AUDIO_CODEC = 2; + + /** + * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via + * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the battery state of + * the device. Will typically mirror battery state reported via intents such as + * {@link android.content.Intent#ACTION_BATTERY_LOW}. + * <p> + * Valid values: + * <UL> + * <LI>{@link #BATTERY_STATE_LOW}</LI> + * <LI>{@link #BATTERY_STATE_GOOD}</LI> + * <LI>{@link #BATTERY_STATE_CHARGING}</LI> + * </UL> + */ + public static final int MESSAGE_DEVICE_BATTERY_STATE = 3; + + /** + * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via + * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the overall network + * coverage as it pertains to the current call. A {@link CallDiagnosticService} should signal + * poor coverage if the network coverage reaches a level where there is a high probability of + * the call dropping as a result. + * <p> + * Valid values: + * <UL> + * <LI>{@link #COVERAGE_POOR}</LI> + * <LI>{@link #COVERAGE_GOOD}</LI> + * </UL> + */ + public static final int MESSAGE_DEVICE_NETWORK_COVERAGE = 4; + + /**@hide*/ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "MESSAGE_", value = { + MESSAGE_CALL_NETWORK_TYPE, + MESSAGE_CALL_AUDIO_CODEC, + MESSAGE_DEVICE_BATTERY_STATE, + MESSAGE_DEVICE_NETWORK_COVERAGE + }) + public @interface MessageType {} + + /** + * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is low. + */ + public static final int BATTERY_STATE_LOW = 1; + + /** + * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is not low. + */ + public static final int BATTERY_STATE_GOOD = 2; + + /** + * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is charging. + */ + public static final int BATTERY_STATE_CHARGING = 3; + + /** + * Used with {@link #MESSAGE_DEVICE_NETWORK_COVERAGE} to indicate that the coverage is poor. + */ + public static final int COVERAGE_POOR = 1; + + /** + * Used with {@link #MESSAGE_DEVICE_NETWORK_COVERAGE} to indicate that the coverage is good. + */ + public static final int COVERAGE_GOOD = 2; + + private Listener mListener; + private String mCallId; + + /** + * @hide + */ + public void setListener(@NonNull Listener listener) { + mListener = listener; + } + + /** + * Sets the call ID for this {@link CallDiagnostics}. + * @param callId + * @hide + */ + public void setCallId(@NonNull String callId) { + mCallId = callId; + } + + /** + * @return the Telecom call ID for this {@link CallDiagnostics}. + * @hide + */ + public @NonNull String getCallId() { + return mCallId; + } + + /** + * Telecom calls this method when the details of a call changes. + * <p> + * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService}; + * see {@link CallDiagnosticService#getExecutor()} for more information. + */ + public abstract void onCallDetailsChanged(@NonNull android.telecom.Call.Details details); + + /** + * The {@link CallDiagnosticService} implements this method to handle messages received via + * device to device communication. + * <p> + * See {@link #sendDeviceToDeviceMessage(int, int)} for background on device to device + * communication. + * <p> + * The underlying device to device communication protocol assumes that where there the two + * devices communicating are using a different version of the protocol, messages the recipient + * are not aware of are silently discarded. This means an older client talking to a new client + * will not receive newer messages and values sent by the new client. + * <p> + * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService}; + * see {@link CallDiagnosticService#getExecutor()} for more information. + */ + public abstract void onReceiveDeviceToDeviceMessage( + @MessageType int message, + int value); + + /** + * Sends a device to device message to the device on the other end of this call. + * <p> + * Device to device communication is an Android platform feature which supports low bandwidth + * communication between Android devices while they are in a call. The device to device + * communication leverages DTMF tones or RTP header extensions to pass messages. The + * messages are unacknowledged and sent in a best-effort manner. The protocols assume that the + * nature of the message are informational only and are used only to convey basic state + * information between devices. + * <p> + * Device to device messages are intentional simplifications of more rich indicators in the + * platform due to the extreme bandwidth constraints inherent with underlying device to device + * communication transports used by the telephony framework. Device to device communication is + * either accomplished by adding RFC8285 compliant RTP header extensions to the audio packets + * for a call, or using the DTMF digits A-D as a communication pathway. RTP header extension + * packets ride alongside a the audio for a call, and are thus limited to roughly a byte for + * a message. Signalling requirements for DTMF digits place even more significant limitations + * on the amount of information which can be communicated during a call, offering only a few + * bits of potential information per message. The messages and values are constrained in order + * to meet the limited bandwidth inherent with DTMF signalling. + * <p> + * Allowed message types are: + * <ul> + * <li>{@link #MESSAGE_CALL_NETWORK_TYPE}</LI> + * <li>{@link #MESSAGE_CALL_AUDIO_CODEC}</LI> + * <li>{@link #MESSAGE_DEVICE_BATTERY_STATE}</LI> + * <li>{@link #MESSAGE_DEVICE_NETWORK_COVERAGE}</LI> + * </ul> + * @param message The message type to send. + * @param value The message value corresponding to the type. + */ + public final void sendDeviceToDeviceMessage(int message, int value) { + if (mListener != null) { + mListener.onSendDeviceToDeviceMessage(this, message, value); + } + } + + /** + * Telecom calls this method when a GSM or CDMA call disconnects. + * The CallDiagnosticService can return a human readable disconnect message which will be passed + * to the Dialer app as the {@link DisconnectCause#getDescription()}. A dialer app typically + * shows this message at the termination of the call. If {@code null} is returned, the + * disconnect message generated by the telephony stack will be shown instead. + * <p> + * @param disconnectCause the disconnect cause for the call. + * @param preciseDisconnectCause the precise disconnect cause for the call. + * @return the disconnect message to use in place of the default Telephony message, or + * {@code null} if the default message will not be overridden. + * <p> + * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService}; + * see {@link CallDiagnosticService#getExecutor()} for more information. + */ + // TODO: Wire in Telephony support for this. + public abstract @Nullable CharSequence onCallDisconnected( + @Annotation.DisconnectCauses int disconnectCause, + @Annotation.PreciseDisconnectCauses int preciseDisconnectCause); + + /** + * Telecom calls this method when an IMS call disconnects and Telephony has already + * provided the disconnect reason info and disconnect message for the call. The + * {@link CallDiagnosticService} can intercept the raw IMS disconnect reason at this point and + * combine it with other call diagnostic information it is aware of to override the disconnect + * call message if desired. + * + * @param disconnectReason The {@link ImsReasonInfo} associated with the call disconnection. + * @return A user-readable call disconnect message to use in place of the platform-generated + * disconnect message, or {@code null} if the disconnect message should not be overridden. + * <p> + * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService}; + * see {@link CallDiagnosticService#getExecutor()} for more information. + */ + // TODO: Wire in Telephony support for this. + public abstract @Nullable CharSequence onCallDisconnected( + @NonNull ImsReasonInfo disconnectReason); + + /** + * Telecom calls this method when a {@link CallQuality} report is received from the telephony + * stack for a call. + * @param callQuality The call quality report for this call. + * <p> + * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService}; + * see {@link CallDiagnosticService#getExecutor()} for more information. + */ + public abstract void onCallQualityReceived(@NonNull CallQuality callQuality); + + /** + * Signals the active default dialer app to display a call diagnostic message. This can be + * used to report problems encountered during the span of a call. + * <p> + * The {@link CallDiagnosticService} provides a unique client-specific identifier used to + * identify the specific diagnostic message type. + * <p> + * The {@link CallDiagnosticService} should call {@link #clearDiagnosticMessage(int)} when the + * diagnostic condition has cleared. + * @param messageId the unique message identifier. + * @param message a human-readable, localized message to be shown to the user indicating a + * call issue which has occurred, along with potential mitigating actions. + */ + public final void displayDiagnosticMessage(int messageId, @NonNull + CharSequence message) { + if (mListener != null) { + mListener.onDisplayDiagnosticMessage(this, messageId, message); + } + } + + /** + * Signals to the active default dialer that the diagnostic message previously signalled using + * {@link #displayDiagnosticMessage(int, CharSequence)} with the specified messageId is no + * longer applicable (e.g. service has improved, for example. + * @param messageId the message identifier for a message previously shown via + * {@link #displayDiagnosticMessage(int, CharSequence)}. + */ + public final void clearDiagnosticMessage(int messageId) { + if (mListener != null) { + mListener.onClearDiagnosticMessage(this, messageId); + } + } + + /** + * Called by the {@link CallDiagnosticService} to update the call details for this + * {@link CallDiagnostics} based on an update received from Telecom. + * @param newDetails the new call details. + * @hide + */ + public void handleCallUpdated(@NonNull Call.Details newDetails) { + onCallDetailsChanged(newDetails); + } +} diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 335857af8883..6dab6df22cf9 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -947,7 +947,7 @@ public abstract class Connection extends Conferenceable { * {@link CallDiagnosticService} implementation which is active. * <p> * Likewise, if a {@link CallDiagnosticService} sends a message using - * {@link DiagnosticCall#sendDeviceToDeviceMessage(int, int)}, it will be routed to telephony + * {@link CallDiagnostics#sendDeviceToDeviceMessage(int, int)}, it will be routed to telephony * via {@link Connection#onCallEvent(String, Bundle)}. The telephony stack will relay the * message to the other device. * @hide @@ -960,7 +960,7 @@ public abstract class Connection extends Conferenceable { * Sent along with {@link #EVENT_DEVICE_TO_DEVICE_MESSAGE} to indicate the device to device * message type. * - * See {@link DiagnosticCall} for more information. + * See {@link CallDiagnostics} for more information. * @hide */ @SystemApi @@ -971,7 +971,7 @@ public abstract class Connection extends Conferenceable { * Sent along with {@link #EVENT_DEVICE_TO_DEVICE_MESSAGE} to indicate the device to device * message value. * <p> - * See {@link DiagnosticCall} for more information. + * See {@link CallDiagnostics} for more information. * @hide */ @SystemApi diff --git a/telecomm/java/android/telecom/DiagnosticCall.java b/telecomm/java/android/telecom/DiagnosticCall.java index af46b7759fb5..a6b7258052a4 100644 --- a/telecomm/java/android/telecom/DiagnosticCall.java +++ b/telecomm/java/android/telecom/DiagnosticCall.java @@ -16,338 +16,12 @@ package android.telecom; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.SystemApi; -import android.telephony.Annotation; -import android.telephony.CallQuality; -import android.telephony.ims.ImsReasonInfo; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.concurrent.Executor; /** - * A {@link DiagnosticCall} provides a way for a {@link CallDiagnosticService} to receive diagnostic - * information about a mobile call on the device. A {@link DiagnosticCall} is similar to a - * {@link Call}, however it does not expose call control capabilities and exposes extra diagnostic - * and messaging capabilities not present on a {@link Call}. The {@link CallDiagnosticService} - * creates a {@link DiagnosticCall} for each {@link Call} on the device. This means that for each - * in progress call on the device, the {@link CallDiagnosticService} will create an instance of - * {@link DiagnosticCall}. - * <p> - * The {@link CallDiagnosticService} can generate mid-call diagnostic messages using the - * {@link #displayDiagnosticMessage(int, CharSequence)} API which provides the user with valuable - * information about conditions impacting their call and corrective actions. For example, if the - * {@link CallDiagnosticService} determines that conditions on the call are degrading, it can inform - * the user that the call may soon drop and that they can try using a different calling method - * (e.g. VOIP or WIFI). - * <h2>Threading Model</h2> - * All incoming IPC from Telecom in this class will use the same {@link Executor} as the - * {@link CallDiagnosticService}. See {@link CallDiagnosticService#setExecutor(Executor)} for more - * information. + * @deprecated use {@link CallDiagnostics} instead. * @hide */ @SystemApi -public abstract class DiagnosticCall { - - /** - * @hide - */ - public interface Listener { - void onSendDeviceToDeviceMessage(DiagnosticCall diagnosticCall, int message, int value); - void onDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId, - CharSequence message); - void onClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId); - } - - /** - * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via - * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the radio access type - * used for the current call. The call network type communicated here is an intentional - * simplification of the {@link android.telephony.TelephonyManager#getNetworkType(int)} which - * removes some of the resolution inherent in those values; the - * {@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE_CA} value, for example is - * collapsed into the {@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE} value for - * efficiency of transport. For a discussion on the necessity of this simplification, see - * {@link #sendDeviceToDeviceMessage(int, int)}. - * <p> - * Valid values are below: - * <UL> - * <LI>{@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE}</LI> - * <LI>{@link android.telephony.TelephonyManager#NETWORK_TYPE_IWLAN}</LI> - * <LI>{@link android.telephony.TelephonyManager#NETWORK_TYPE_NR}</LI> - * </UL> - */ - public static final int MESSAGE_CALL_NETWORK_TYPE = 1; - - /** - * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via - * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the call audio codec - * used for the current call. - * <p> - * The audio codec communicated here is an intentional simplification of the - * {@link Connection#EXTRA_AUDIO_CODEC} for a call and focuses on communicating the most common - * variants of these audio codecs. Other variants of these codecs are reported as the next - * closest variant. For example, the {@link Connection#AUDIO_CODEC_EVS_FB} full band codec - * is reported via device to device communication as {@link Connection#AUDIO_CODEC_EVS_WB}. - * For a discussion on the necessity of this simplification, see - * {@link #sendDeviceToDeviceMessage(int, int)}. - * <p> - * Valid values: - * <UL> - * <LI>{@link Connection#AUDIO_CODEC_EVS_WB}</LI> - * <LI>{@link Connection#AUDIO_CODEC_AMR_WB}</LI> - * <LI>{@link Connection#AUDIO_CODEC_AMR}</LI> - * </UL> - */ - public static final int MESSAGE_CALL_AUDIO_CODEC = 2; - - /** - * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via - * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the battery state of - * the device. Will typically mirror battery state reported via intents such as - * {@link android.content.Intent#ACTION_BATTERY_LOW}. - * <p> - * Valid values: - * <UL> - * <LI>{@link #BATTERY_STATE_LOW}</LI> - * <LI>{@link #BATTERY_STATE_GOOD}</LI> - * <LI>{@link #BATTERY_STATE_CHARGING}</LI> - * </UL> - */ - public static final int MESSAGE_DEVICE_BATTERY_STATE = 3; - - /** - * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via - * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the overall network - * coverage as it pertains to the current call. A {@link CallDiagnosticService} should signal - * poor coverage if the network coverage reaches a level where there is a high probability of - * the call dropping as a result. - * <p> - * Valid values: - * <UL> - * <LI>{@link #COVERAGE_POOR}</LI> - * <LI>{@link #COVERAGE_GOOD}</LI> - * </UL> - */ - public static final int MESSAGE_DEVICE_NETWORK_COVERAGE = 4; - - /**@hide*/ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = "MESSAGE_", value = { - MESSAGE_CALL_NETWORK_TYPE, - MESSAGE_CALL_AUDIO_CODEC, - MESSAGE_DEVICE_BATTERY_STATE, - MESSAGE_DEVICE_NETWORK_COVERAGE - }) - public @interface MessageType {} - - /** - * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is low. - */ - public static final int BATTERY_STATE_LOW = 1; - - /** - * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is not low. - */ - public static final int BATTERY_STATE_GOOD = 2; - - /** - * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is charging. - */ - public static final int BATTERY_STATE_CHARGING = 3; - - /** - * Used with {@link #MESSAGE_DEVICE_NETWORK_COVERAGE} to indicate that the coverage is poor. - */ - public static final int COVERAGE_POOR = 1; - - /** - * Used with {@link #MESSAGE_DEVICE_NETWORK_COVERAGE} to indicate that the coverage is good. - */ - public static final int COVERAGE_GOOD = 2; - - private Listener mListener; - private String mCallId; - - /** - * @hide - */ - public void setListener(@NonNull Listener listener) { - mListener = listener; - } - - /** - * Sets the call ID for this {@link DiagnosticCall}. - * @param callId - * @hide - */ - public void setCallId(@NonNull String callId) { - mCallId = callId; - } - - /** - * @return the Telecom call ID for this {@link DiagnosticCall}. - * @hide - */ - public @NonNull String getCallId() { - return mCallId; - } - - /** - * Telecom calls this method when the details of a call changes. - * <p> - * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService}; - * see {@link CallDiagnosticService#getExecutor()} for more information. - */ - public abstract void onCallDetailsChanged(@NonNull android.telecom.Call.Details details); - - /** - * The {@link CallDiagnosticService} implements this method to handle messages received via - * device to device communication. - * <p> - * See {@link #sendDeviceToDeviceMessage(int, int)} for background on device to device - * communication. - * <p> - * The underlying device to device communication protocol assumes that where there the two - * devices communicating are using a different version of the protocol, messages the recipient - * are not aware of are silently discarded. This means an older client talking to a new client - * will not receive newer messages and values sent by the new client. - * <p> - * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService}; - * see {@link CallDiagnosticService#getExecutor()} for more information. - */ - public abstract void onReceiveDeviceToDeviceMessage( - @MessageType int message, - int value); - - /** - * Sends a device to device message to the device on the other end of this call. - * <p> - * Device to device communication is an Android platform feature which supports low bandwidth - * communication between Android devices while they are in a call. The device to device - * communication leverages DTMF tones or RTP header extensions to pass messages. The - * messages are unacknowledged and sent in a best-effort manner. The protocols assume that the - * nature of the message are informational only and are used only to convey basic state - * information between devices. - * <p> - * Device to device messages are intentional simplifications of more rich indicators in the - * platform due to the extreme bandwidth constraints inherent with underlying device to device - * communication transports used by the telephony framework. Device to device communication is - * either accomplished by adding RFC8285 compliant RTP header extensions to the audio packets - * for a call, or using the DTMF digits A-D as a communication pathway. RTP header extension - * packets ride alongside a the audio for a call, and are thus limited to roughly a byte for - * a message. Signalling requirements for DTMF digits place even more significant limitations - * on the amount of information which can be communicated during a call, offering only a few - * bits of potential information per message. The messages and values are constrained in order - * to meet the limited bandwidth inherent with DTMF signalling. - * <p> - * Allowed message types are: - * <ul> - * <li>{@link #MESSAGE_CALL_NETWORK_TYPE}</LI> - * <li>{@link #MESSAGE_CALL_AUDIO_CODEC}</LI> - * <li>{@link #MESSAGE_DEVICE_BATTERY_STATE}</LI> - * <li>{@link #MESSAGE_DEVICE_NETWORK_COVERAGE}</LI> - * </ul> - * @param message The message type to send. - * @param value The message value corresponding to the type. - */ - public final void sendDeviceToDeviceMessage(int message, int value) { - if (mListener != null) { - mListener.onSendDeviceToDeviceMessage(this, message, value); - } - } - - /** - * Telecom calls this method when a GSM or CDMA call disconnects. - * The CallDiagnosticService can return a human readable disconnect message which will be passed - * to the Dialer app as the {@link DisconnectCause#getDescription()}. A dialer app typically - * shows this message at the termination of the call. If {@code null} is returned, the - * disconnect message generated by the telephony stack will be shown instead. - * <p> - * @param disconnectCause the disconnect cause for the call. - * @param preciseDisconnectCause the precise disconnect cause for the call. - * @return the disconnect message to use in place of the default Telephony message, or - * {@code null} if the default message will not be overridden. - * <p> - * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService}; - * see {@link CallDiagnosticService#getExecutor()} for more information. - */ - // TODO: Wire in Telephony support for this. - public abstract @Nullable CharSequence onCallDisconnected( - @Annotation.DisconnectCauses int disconnectCause, - @Annotation.PreciseDisconnectCauses int preciseDisconnectCause); - - /** - * Telecom calls this method when an IMS call disconnects and Telephony has already - * provided the disconnect reason info and disconnect message for the call. The - * {@link CallDiagnosticService} can intercept the raw IMS disconnect reason at this point and - * combine it with other call diagnostic information it is aware of to override the disconnect - * call message if desired. - * - * @param disconnectReason The {@link ImsReasonInfo} associated with the call disconnection. - * @return A user-readable call disconnect message to use in place of the platform-generated - * disconnect message, or {@code null} if the disconnect message should not be overridden. - * <p> - * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService}; - * see {@link CallDiagnosticService#getExecutor()} for more information. - */ - // TODO: Wire in Telephony support for this. - public abstract @Nullable CharSequence onCallDisconnected( - @NonNull ImsReasonInfo disconnectReason); - - /** - * Telecom calls this method when a {@link CallQuality} report is received from the telephony - * stack for a call. - * @param callQuality The call quality report for this call. - * <p> - * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService}; - * see {@link CallDiagnosticService#getExecutor()} for more information. - */ - public abstract void onCallQualityReceived(@NonNull CallQuality callQuality); - - /** - * Signals the active default dialer app to display a call diagnostic message. This can be - * used to report problems encountered during the span of a call. - * <p> - * The {@link CallDiagnosticService} provides a unique client-specific identifier used to - * identify the specific diagnostic message type. - * <p> - * The {@link CallDiagnosticService} should call {@link #clearDiagnosticMessage(int)} when the - * diagnostic condition has cleared. - * @param messageId the unique message identifier. - * @param message a human-readable, localized message to be shown to the user indicating a - * call issue which has occurred, along with potential mitigating actions. - */ - public final void displayDiagnosticMessage(int messageId, @NonNull - CharSequence message) { - if (mListener != null) { - mListener.onDisplayDiagnosticMessage(this, messageId, message); - } - } - - /** - * Signals to the active default dialer that the diagnostic message previously signalled using - * {@link #displayDiagnosticMessage(int, CharSequence)} with the specified messageId is no - * longer applicable (e.g. service has improved, for example. - * @param messageId the message identifier for a message previously shown via - * {@link #displayDiagnosticMessage(int, CharSequence)}. - */ - public final void clearDiagnosticMessage(int messageId) { - if (mListener != null) { - mListener.onClearDiagnosticMessage(this, messageId); - } - } - - /** - * Called by the {@link CallDiagnosticService} to update the call details for this - * {@link DiagnosticCall} based on an update received from Telecom. - * @param newDetails the new call details. - * @hide - */ - public void handleCallUpdated(@NonNull Call.Details newDetails) { - onCallDetailsChanged(newDetails); - } +public abstract class DiagnosticCall extends CallDiagnostics { } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 45eafa45c78d..74421a08b22d 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2974,6 +2974,18 @@ public class CarrierConfigManager { public static final String KEY_RTT_SUPPORTED_FOR_VT_BOOL = "rtt_supported_for_vt_bool"; /** + * Indicates if the carrier supports upgrading a call that was previously an RTT call to VT. + */ + public static final String KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL = + "vt_upgrade_supported_for_downgraded_rtt_call"; + + /** + * Indicates if the carrier supports upgrading a call that was previously a VT call to RTT. + */ + public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = + "rtt_upgrade_supported_for_downgraded_vt_call"; + + /** * Indicates if the carrier supports upgrading a voice call to an RTT call during the call. */ public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool"; @@ -4065,6 +4077,22 @@ public class CarrierConfigManager { "is_opportunistic_subscription_bool"; /** + * The flatten string {@link android.content.ComponentName componentName} of carrier + * provisioning app receiver. + * + * <p> + * The RadioInfo activity(*#*#INFO#*#*) will broadcast an intent to this receiver when the + * "Carrier Provisioning Info" or "Trigger Carrier Provisioning" button clicked. + * + * <p> + * e.g, com.google.android.carrierPackageName/.CarrierReceiverName + * + * @hide + */ + public static final String KEY_CARRIER_PROVISIONING_APP_STRING = + "carrier_provisioning_app_string"; + + /** * Configs used by the IMS stack. */ public static final class Ims { @@ -5156,6 +5184,8 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_TTY_SUPPORTED_BOOL, true); sDefaults.putBoolean(KEY_HIDE_TTY_HCO_VCO_WITH_RTT_BOOL, false); sDefaults.putBoolean(KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL, false); + sDefaults.putBoolean(KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL, true); + sDefaults.putBoolean(KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL, true); sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false); sDefaults.putBoolean(KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL, true); sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null); @@ -5360,6 +5390,7 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY, new String[]{"ia", "default", "ims", "mms", "dun", "emergency"}); sDefaults.putBoolean(KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL, false); + sDefaults.putString(KEY_CARRIER_PROVISIONING_APP_STRING, ""); } /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index faa5deed0f1c..962200b82a81 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -15454,7 +15454,9 @@ public class TelephonyManager { public static final int PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED = 1; /** - * The unattended reboot was not prepared due to generic error. + * The unattended reboot was not prepared due to a non-recoverable error. After this error, + * the client that manages the unattended reboot should not try to invoke the API again + * until the next power cycle. * @hide */ @SystemApi diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index b30dd2697645..96af172e489e 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -54,6 +54,7 @@ import android.telephony.TelephonyHistogram; import android.telephony.VisualVoicemailSmsFilterSettings; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.RcsClientConfiguration; +import android.telephony.ims.RcsContactUceCapability; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IImsConfig; import android.telephony.ims.aidl.IImsConfigCallback; @@ -2374,6 +2375,41 @@ interface ITelephony { void setDeviceUceEnabled(boolean isEnabled); /** + * Add feature tags to the IMS registration being tracked by UCE and potentially + * generate a new PUBLISH to the network. + * Note: This is designed for a SHELL command only. + */ + RcsContactUceCapability addUceRegistrationOverrideShell(int subId, in List<String> featureTags); + + /** + * Remove feature tags from the IMS registration being tracked by UCE and potentially + * generate a new PUBLISH to the network. + * Note: This is designed for a SHELL command only. + */ + RcsContactUceCapability removeUceRegistrationOverrideShell(int subId, + in List<String> featureTags); + + /** + * Clear overridden feature tags in the IMS registration being tracked by UCE and potentially + * generate a new PUBLISH to the network. + * Note: This is designed for a SHELL command only. + */ + RcsContactUceCapability clearUceRegistrationOverrideShell(int subId); + + /** + * Get the latest RcsContactUceCapability structure that is used in SIP PUBLISH procedures. + * Note: This is designed for a SHELL command only. + */ + RcsContactUceCapability getLatestRcsContactUceCapabilityShell(int subId); + + /** + * Returns the last PIDF XML sent to the network during the last PUBLISH or "none" if the + * device does not have an active PUBLISH. + * Note: This is designed for a SHELL command only. + */ + String getLastUcePidfXmlShell(int subId); + + /** * Set a SignalStrengthUpdateRequest to receive notification when Signal Strength breach the * specified thresholds. */ diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index 3c12aaa0f1c8..c92d40cdd555 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt @@ -16,7 +16,6 @@ package com.android.server.wm.flicker.close -import android.view.WindowManagerPolicyConstants import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -50,12 +49,7 @@ class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio @JvmStatic fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 5, - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) - ) + .getConfigNonRotationTests(repetitions = 5) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt index 8359ccf12d2d..1f880f61d65e 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt @@ -16,7 +16,6 @@ package com.android.server.wm.flicker.close -import android.view.WindowManagerPolicyConstants import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -50,12 +49,7 @@ class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 5, - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) - ) + .getConfigNonRotationTests(repetitions = 5) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt index aad06ee94914..d994eec6ff33 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt @@ -19,8 +19,6 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit -import android.view.Surface -import android.view.WindowManagerPolicyConstants import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry @@ -30,13 +28,10 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales @@ -64,23 +59,15 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } setup { - test { - device.wakeUpAndGoToHomeScreen() - } eachRun { testApp.launchViaIntent(wmHelper) testApp.openIME(device, wmHelper) - this.setRotation(testSpec.config.startRotation) } } teardown { test { - testApp.exit() - wmHelper.waitForAppTransitionIdle() - this.setRotation(Surface.ROTATION_0) + testApp.exit(wmHelper) } } transitions { @@ -172,12 +159,7 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 5, - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) - ) + .getConfigNonRotationTests(repetitions = 5) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index d08e7e2e5191..01d260606214 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -19,7 +19,6 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation import android.platform.test.annotations.Presubmit import android.view.Surface -import android.view.WindowManagerPolicyConstants import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry @@ -29,13 +28,10 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales @@ -63,22 +59,15 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } setup { - test { - device.wakeUpAndGoToHomeScreen() - } eachRun { testApp.launchViaIntent(wmHelper) testApp.openIME(device, wmHelper) - this.setRotation(testSpec.config.startRotation) } } teardown { test { - testApp.exit() - this.setRotation(Surface.ROTATION_0) + testApp.exit(wmHelper) } } transitions { @@ -189,12 +178,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 5, - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) - ) + .getConfigNonRotationTests(repetitions = 5) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt index 19dec7abee54..6b8bf63aa926 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt @@ -18,8 +18,7 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation import android.platform.test.annotations.Presubmit -import android.view.Surface -import android.view.WindowManagerPolicyConstants +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider @@ -28,16 +27,14 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible +import org.junit.Assume import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import org.junit.FixMethodOrder import org.junit.Test @@ -60,13 +57,9 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } setup { test { - device.wakeUpAndGoToHomeScreen() testApp.launchViaIntent() - this.setRotation(testSpec.config.startRotation) } eachRun { testApp.openIME(device, wmHelper) @@ -74,8 +67,7 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { } teardown { test { - testApp.exit() - this.setRotation(Surface.ROTATION_0) + testApp.exit(wmHelper) } } transitions { @@ -120,13 +112,31 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun navBarLayerRotatesAndScales() = + fun navBarLayerRotatesAndScales() { + Assume.assumeFalse(testSpec.isRotated) testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) + } + + @FlakyTest + @Test + fun navBarLayerRotatesAndScales_Flaky() { + Assume.assumeTrue(testSpec.isRotated) + testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) + } @Presubmit @Test - fun statusBarLayerRotatesScales() = + fun statusBarLayerRotatesScales() { + Assume.assumeFalse(testSpec.isRotated) testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation) + } + + @FlakyTest + @Test + fun statusBarLayerRotatesScales_Flaky() { + Assume.assumeTrue(testSpec.isRotated) + testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation) + } @Presubmit @Test @@ -149,12 +159,7 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 5, - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) - ) + .getConfigNonRotationTests(repetitions = 5) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt index b214414ab7f1..99cedaf3f5a7 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt @@ -29,13 +29,10 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible @@ -61,15 +58,9 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } setup { - test { - device.wakeUpAndGoToHomeScreen() - } eachRun { testApp.launchViaIntent(wmHelper) - this.setRotation(testSpec.config.startRotation) testApp.openIME(device, wmHelper) } } @@ -85,7 +76,6 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { } test { testApp.exit() - this.setRotation(Surface.ROTATION_0) } } } @@ -175,6 +165,7 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { repetitions = 5, supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY ) ) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt index ab5e9b40463e..f8a442980978 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt @@ -28,8 +28,6 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible @@ -37,7 +35,6 @@ import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.appWindowAlwaysVisibleOnTop import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.layerAlwaysVisible -import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales @@ -64,13 +61,9 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } setup { test { - device.wakeUpAndGoToHomeScreen() testApp.launchViaIntent(wmHelper) - this.setRotation(testSpec.config.startRotation) } } transitions { @@ -82,7 +75,6 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { } test { testApp.exit() - this.setRotation(Surface.ROTATION_0) } } } @@ -177,6 +169,7 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { repetitions = 5, supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY ) ) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt index 52a826655993..f98581967596 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt @@ -30,7 +30,6 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible @@ -38,7 +37,6 @@ import com.android.server.wm.flicker.wallpaperWindowBecomesInvisible import com.android.server.wm.flicker.appLayerReplacesWallpaperLayer import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible @@ -68,11 +66,8 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } setup { test { - device.wakeUpAndGoToHomeScreen() testApp.launchViaIntent(wmHelper) testApp.openIME(device, wmHelper) } @@ -89,7 +84,6 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { } teardown { test { - this.setRotation(Surface.ROTATION_0) testApp.exit() } } @@ -192,6 +186,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { .getConfigNonRotationTests( repetitions = 1, supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY ) ) diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index fadd1eac14ef..c08b723972b6 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -23,6 +23,8 @@ import static android.content.Intent.ACTION_USER_ADDED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.ACTION_USER_UNLOCKED; import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; +import static android.content.pm.PackageManager.FEATURE_WIFI; +import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.PERMISSION_DENIED; @@ -35,11 +37,14 @@ import static android.net.ConnectivityManager.NETID_UNSET; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; +import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT; +import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE; import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL; +import static android.net.ConnectivityManager.TYPE_PROXY; import static android.net.ConnectivityManager.TYPE_VPN; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS; @@ -82,10 +87,10 @@ import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; -import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED; -import static android.net.NetworkPolicyManager.RULE_NONE; -import static android.net.NetworkPolicyManager.RULE_REJECT_ALL; -import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; +import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_DATA_SAVER; +import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_USER_RESTRICTED; +import static android.net.NetworkPolicyManager.BLOCKED_REASON_BATTERY_SAVER; +import static android.net.NetworkPolicyManager.BLOCKED_REASON_NONE; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; @@ -182,8 +187,7 @@ import android.net.IDnsResolver; import android.net.INetd; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; -import android.net.INetworkPolicyListener; -import android.net.IOnSetOemNetworkPreferenceListener; +import android.net.IOnCompleteListener; import android.net.IQosCallback; import android.net.InetAddresses; import android.net.InterfaceConfigurationParcel; @@ -201,7 +205,9 @@ import android.net.NetworkFactory; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkPolicyManager; +import android.net.NetworkPolicyManager.NetworkPolicyCallback; import android.net.NetworkRequest; +import android.net.NetworkScore; import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkStackClient; @@ -377,6 +383,11 @@ public class ConnectivityServiceTest { // Set a non-zero value to verify the flow to set tcp init rwnd value. private static final int TEST_TCP_INIT_RWND = 60; + // Used for testing the per-work-profile default network. + private static final int TEST_APP_ID = 103; + private static final int TEST_WORK_PROFILE_USER_ID = 2; + private static final int TEST_WORK_PROFILE_APP_UID = + UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID); private static final String CLAT_PREFIX = "v4-"; private static final String MOBILE_IFNAME = "test_rmnet_data0"; private static final String WIFI_IFNAME = "test_wlan0"; @@ -411,7 +422,7 @@ public class ConnectivityServiceTest { private TestNetworkAgentWrapper mEthernetNetworkAgent; private MockVpn mMockVpn; private Context mContext; - private INetworkPolicyListener mPolicyListener; + private NetworkPolicyCallback mPolicyCallback; private WrappedMultinetworkPolicyTracker mPolicyTracker; private HandlerThread mAlarmManagerThread; private TestNetIdManager mNetIdManager; @@ -420,10 +431,10 @@ public class ConnectivityServiceTest { private VpnManagerService mVpnManagerService; private TestNetworkCallback mDefaultNetworkCallback; private TestNetworkCallback mSystemDefaultNetworkCallback; + private TestNetworkCallback mProfileDefaultNetworkCallback; // State variables required to emulate NetworkPolicyManagerService behaviour. - private int mUidRules = RULE_NONE; - private boolean mRestrictBackground = false; + private int mBlockedReasons = BLOCKED_REASON_NONE; @Mock DeviceIdleInternal mDeviceIdleInternal; @Mock INetworkManagementService mNetworkManagementService; @@ -541,13 +552,26 @@ public class ConnectivityServiceTest { return super.getSystemService(name); } + final HashMap<UserHandle, UserManager> mUserManagers = new HashMap<>(); @Override public Context createContextAsUser(UserHandle user, int flags) { final Context asUser = mock(Context.class, AdditionalAnswers.delegatesTo(this)); doReturn(user).when(asUser).getUser(); + doAnswer((inv) -> { + final UserManager um = mUserManagers.computeIfAbsent(user, + u -> mock(UserManager.class, AdditionalAnswers.delegatesTo(mUserManager))); + return um; + }).when(asUser).getSystemService(Context.USER_SERVICE); return asUser; } + public void setWorkProfile(@NonNull final UserHandle userHandle, boolean value) { + // This relies on all contexts for a given user returning the same UM mock + final UserManager umMock = createContextAsUser(userHandle, 0 /* flags */) + .getSystemService(UserManager.class); + doReturn(value).when(umMock).isManagedProfile(); + } + @Override public ContentResolver getContentResolver() { return mContentResolver; @@ -1078,6 +1102,10 @@ public class ConnectivityServiceTest { public void triggerUnfulfillable(NetworkRequest r) { super.releaseRequestAsUnfulfillableByAnyFactory(r); } + + public void assertNoRequestChanged() { + assertNull(mRequestHistory.poll(0, r -> true)); + } } private Set<UidRange> uidRangesForUids(int... uids) { @@ -1345,28 +1373,13 @@ public class ConnectivityServiceTest { } private void mockUidNetworkingBlocked() { - doAnswer(i -> mContext.getSystemService(NetworkPolicyManager.class) - .checkUidNetworkingBlocked(i.getArgument(0) /* uid */, mUidRules, - i.getArgument(1) /* metered */, mRestrictBackground) + doAnswer(i -> NetworkPolicyManager.isUidBlocked(mBlockedReasons, i.getArgument(1)) ).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean()); - - doAnswer(inv -> mContext.getSystemService(NetworkPolicyManager.class) - .checkUidNetworkingBlocked(inv.getArgument(0) /* uid */, - inv.getArgument(1) /* uidRules */, - inv.getArgument(2) /* isNetworkMetered */, - inv.getArgument(3) /* isBackgroundRestricted */) - ).when(mNetworkPolicyManager).checkUidNetworkingBlocked( - anyInt(), anyInt(), anyBoolean(), anyBoolean()); } - private void setUidRulesChanged(int uidRules) throws RemoteException { - mUidRules = uidRules; - mPolicyListener.onUidRulesChanged(Process.myUid(), mUidRules); - } - - private void setRestrictBackgroundChanged(boolean restrictBackground) throws RemoteException { - mRestrictBackground = restrictBackground; - mPolicyListener.onRestrictBackgroundChanged(mRestrictBackground); + private void setBlockedReasonChanged(int blockedReasons) { + mBlockedReasons = blockedReasons; + mPolicyCallback.onUidBlockedReasonChanged(Process.myUid(), blockedReasons); } private Nat464Xlat getNat464Xlat(NetworkAgentWrapper mna) { @@ -1403,17 +1416,36 @@ public class ConnectivityServiceTest { fail("ConditionVariable was blocked for more than " + TIMEOUT_MS + "ms"); } - private void registerNetworkCallbackAsUid(NetworkRequest request, NetworkCallback callback, - int uid) { + private <T> T doAsUid(final int uid, @NonNull final Supplier<T> what) { when(mDeps.getCallingUid()).thenReturn(uid); try { - mCm.registerNetworkCallback(request, callback); - waitForIdle(); + return what.get(); } finally { returnRealCallingUid(); } } + private void doAsUid(final int uid, @NonNull final Runnable what) { + doAsUid(uid, () -> { + what.run(); return Void.TYPE; + }); + } + + private void registerNetworkCallbackAsUid(NetworkRequest request, NetworkCallback callback, + int uid) { + doAsUid(uid, () -> { + mCm.registerNetworkCallback(request, callback); + }); + } + + private void registerDefaultNetworkCallbackAsUid(@NonNull final NetworkCallback callback, + final int uid) { + doAsUid(uid, () -> { + mCm.registerDefaultNetworkCallback(callback); + waitForIdle(); + }); + } + private static final int PRIMARY_USER = 0; private static final int APP1_UID = UserHandle.getUid(PRIMARY_USER, 10100); private static final int APP2_UID = UserHandle.getUid(PRIMARY_USER, 10101); @@ -1460,6 +1492,9 @@ public class ConnectivityServiceTest { Looper.prepare(); } mockDefaultPackages(); + mockHasSystemFeature(FEATURE_WIFI, true); + mockHasSystemFeature(FEATURE_WIFI_DIRECT, true); + doReturn(true).when(mTelephonyManager).isDataCapable(); FakeSettingsProvider.clearSettingsProvider(); mServiceContext = new MockContext(InstrumentationRegistry.getContext(), @@ -1486,10 +1521,11 @@ public class ConnectivityServiceTest { mService.mNascentDelayMs = TEST_NASCENT_DELAY_MS; verify(mDeps).makeMultinetworkPolicyTracker(any(), any(), any()); - final ArgumentCaptor<INetworkPolicyListener> policyListenerCaptor = - ArgumentCaptor.forClass(INetworkPolicyListener.class); - verify(mNetworkPolicyManager).registerListener(policyListenerCaptor.capture()); - mPolicyListener = policyListenerCaptor.getValue(); + final ArgumentCaptor<NetworkPolicyCallback> policyCallbackCaptor = + ArgumentCaptor.forClass(NetworkPolicyCallback.class); + verify(mNetworkPolicyManager).registerNetworkPolicyCallback(any(), + policyCallbackCaptor.capture()); + mPolicyCallback = policyCallbackCaptor.getValue(); // Create local CM before sending system ready so that we can answer // getSystemService() correctly. @@ -1512,10 +1548,7 @@ public class ConnectivityServiceTest { } private ConnectivityService.Dependencies makeDependencies() { - doReturn(TEST_TCP_INIT_RWND).when(mSystemProperties) - .getInt("net.tcp.default_init_rwnd", 0); doReturn(false).when(mSystemProperties).getBoolean("ro.radio.noril", false); - doNothing().when(mSystemProperties).setTcpInitRwnd(anyInt()); final ConnectivityService.Dependencies deps = mock(ConnectivityService.Dependencies.class); doReturn(mCsHandlerThread).when(deps).makeHandlerThread(); doReturn(mNetIdManager).when(deps).makeNetIdManager(); @@ -1573,6 +1606,7 @@ public class ConnectivityServiceTest { @After public void tearDown() throws Exception { unregisterDefaultNetworkCallbacks(); + maybeTearDownEnterpriseNetwork(); setAlwaysOnNetworks(false); if (mCellNetworkAgent != null) { mCellNetworkAgent.disconnect(); @@ -1783,7 +1817,8 @@ public class ConnectivityServiceTest { assertTrue(mCm.isNetworkSupported(TYPE_WIFI)); assertTrue(mCm.isNetworkSupported(TYPE_MOBILE)); assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_MMS)); - assertFalse(mCm.isNetworkSupported(TYPE_MOBILE_FOTA)); + assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_FOTA)); + assertFalse(mCm.isNetworkSupported(TYPE_PROXY)); // Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our // mocks, this assert exercises the ConnectivityService code path that ensures that @@ -7213,7 +7248,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertExtraInfoFromCmPresent(mCellNetworkAgent); - setUidRulesChanged(RULE_REJECT_ALL); + setBlockedReasonChanged(BLOCKED_REASON_BATTERY_SAVER); cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); assertNull(mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); @@ -7221,17 +7256,17 @@ public class ConnectivityServiceTest { assertExtraInfoFromCmBlocked(mCellNetworkAgent); // ConnectivityService should cache it not to invoke the callback again. - setUidRulesChanged(RULE_REJECT_METERED); + setBlockedReasonChanged(BLOCKED_METERED_REASON_USER_RESTRICTED); cellNetworkCallback.assertNoCallback(); - setUidRulesChanged(RULE_NONE); + setBlockedReasonChanged(BLOCKED_REASON_NONE); cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertExtraInfoFromCmPresent(mCellNetworkAgent); - setUidRulesChanged(RULE_REJECT_METERED); + setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER); cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); assertNull(mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); @@ -7256,33 +7291,33 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertExtraInfoFromCmBlocked(mCellNetworkAgent); - setUidRulesChanged(RULE_ALLOW_METERED); + setBlockedReasonChanged(BLOCKED_REASON_NONE); cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertExtraInfoFromCmPresent(mCellNetworkAgent); - setUidRulesChanged(RULE_NONE); + setBlockedReasonChanged(BLOCKED_REASON_NONE); cellNetworkCallback.assertNoCallback(); // Restrict background data. Networking is not blocked because the network is unmetered. - setRestrictBackgroundChanged(true); + setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER); cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); assertNull(mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertExtraInfoFromCmBlocked(mCellNetworkAgent); - setRestrictBackgroundChanged(true); + setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER); cellNetworkCallback.assertNoCallback(); - setUidRulesChanged(RULE_ALLOW_METERED); + setBlockedReasonChanged(BLOCKED_REASON_NONE); cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertExtraInfoFromCmPresent(mCellNetworkAgent); - setRestrictBackgroundChanged(false); + setBlockedReasonChanged(BLOCKED_REASON_NONE); cellNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); @@ -7299,9 +7334,9 @@ public class ConnectivityServiceTest { mockUidNetworkingBlocked(); // No Networkcallbacks invoked before any network is active. - setUidRulesChanged(RULE_REJECT_ALL); - setUidRulesChanged(RULE_NONE); - setUidRulesChanged(RULE_REJECT_METERED); + setBlockedReasonChanged(BLOCKED_REASON_BATTERY_SAVER); + setBlockedReasonChanged(BLOCKED_REASON_NONE); + setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER); defaultCallback.assertNoCallback(); mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); @@ -7326,8 +7361,8 @@ public class ConnectivityServiceTest { defaultCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); // Verify there's no Networkcallbacks invoked after data saver on/off. - setRestrictBackgroundChanged(true); - setRestrictBackgroundChanged(false); + setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER); + setBlockedReasonChanged(BLOCKED_REASON_NONE); defaultCallback.assertNoCallback(); mCellNetworkAgent.disconnect(); @@ -7992,7 +8027,6 @@ public class ConnectivityServiceTest { // Switching default network updates TCP buffer sizes. verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES); - verify(mSystemProperties, times(1)).setTcpInitRwnd(eq(TEST_TCP_INIT_RWND)); // Add an IPv4 address. Expect prefix discovery to be stopped. Netd doesn't tell us that // the NAT64 prefix was removed because one was never discovered. cellLp.addLinkAddress(myIpv4); @@ -8478,14 +8512,12 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(false); networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES); - verify(mSystemProperties, times(1)).setTcpInitRwnd(eq(TEST_TCP_INIT_RWND)); // Change link Properties should have updated tcp buffer size. LinkProperties lp = new LinkProperties(); lp.setTcpBufferSizes(testTcpBufferSizes); mCellNetworkAgent.sendLinkProperties(lp); networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); verifyTcpBufferSizeChange(testTcpBufferSizes); - verify(mSystemProperties, times(2)).setTcpInitRwnd(eq(TEST_TCP_INIT_RWND)); // Clean up. mCellNetworkAgent.disconnect(); networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); @@ -9160,7 +9192,8 @@ public class ConnectivityServiceTest { ConnectivityManager.getNetworkTypeName(TYPE_MOBILE), TelephonyManager.getNetworkTypeName(TelephonyManager.NETWORK_TYPE_LTE)); return new NetworkAgentInfo(null, new Network(NET_ID), info, new LinkProperties(), - nc, 0, mServiceContext, null, new NetworkAgentConfig(), mService, null, null, 0, + nc, new NetworkScore.Builder().setLegacyInt(0).build(), + mServiceContext, null, new NetworkAgentConfig(), mService, null, null, 0, INVALID_UID, mQosCallbackTracker, new ConnectivityService.Dependencies()); } @@ -10112,9 +10145,12 @@ public class ConnectivityServiceTest { Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); mSystemDefaultNetworkCallback = new TestNetworkCallback(); mDefaultNetworkCallback = new TestNetworkCallback(); + mProfileDefaultNetworkCallback = new TestNetworkCallback(); mCm.registerSystemDefaultNetworkCallback(mSystemDefaultNetworkCallback, new Handler(ConnectivityThread.getInstanceLooper())); mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback); + registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback, + TEST_WORK_PROFILE_APP_UID); mServiceContext.setPermission( Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED); } @@ -10126,6 +10162,9 @@ public class ConnectivityServiceTest { if (null != mSystemDefaultNetworkCallback) { mCm.unregisterNetworkCallback(mSystemDefaultNetworkCallback); } + if (null != mProfileDefaultNetworkCallback) { + mCm.unregisterNetworkCallback(mProfileDefaultNetworkCallback); + } } private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest( @@ -10181,7 +10220,7 @@ public class ConnectivityServiceTest { oemPrefListener.expectOnComplete(); } - private static class TestOemListenerCallback implements IOnSetOemNetworkPreferenceListener { + private static class TestOemListenerCallback implements IOnCompleteListener { final CompletableFuture<Object> mDone = new CompletableFuture<>(); @Override @@ -11119,11 +11158,495 @@ public class ConnectivityServiceTest { mCm.unregisterNetworkCallback(cellCb); } + // Cannot be part of MockNetworkFactory since it requires method of the test. + private void expectNoRequestChanged(@NonNull MockNetworkFactory factory) { + waitForIdle(); + factory.assertNoRequestChanged(); + } + @Test - public void testRegisterBestMatchingNetworkCallback() throws Exception { - final NetworkRequest request = new NetworkRequest.Builder().build(); - assertThrows(UnsupportedOperationException.class, - () -> mCm.registerBestMatchingNetworkCallback(request, new NetworkCallback(), - mCsHandlerThread.getThreadHandler())); + public void testRegisterBestMatchingNetworkCallback_noIssueToFactory() throws Exception { + // Prepare mock mms factory. + final HandlerThread handlerThread = new HandlerThread("MockCellularFactory"); + handlerThread.start(); + NetworkCapabilities filter = new NetworkCapabilities() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_MMS); + final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), + mServiceContext, "testFactory", filter, mCsHandlerThread); + testFactory.setScoreFilter(40); + + try { + // Register the factory and expect it will see default request, because all requests + // are sent to all factories. + testFactory.register(); + testFactory.expectRequestAdd(); + testFactory.assertRequestCountEquals(1); + // The factory won't try to start the network since the default request doesn't + // match the filter (no INTERNET capability). + assertFalse(testFactory.getMyStartRequested()); + + // Register callback for listening best matching network. Verify that the request won't + // be sent to factory. + final TestNetworkCallback bestMatchingCb = new TestNetworkCallback(); + mCm.registerBestMatchingNetworkCallback( + new NetworkRequest.Builder().addCapability(NET_CAPABILITY_MMS).build(), + bestMatchingCb, mCsHandlerThread.getThreadHandler()); + bestMatchingCb.assertNoCallback(); + expectNoRequestChanged(testFactory); + testFactory.assertRequestCountEquals(1); + assertFalse(testFactory.getMyStartRequested()); + + // Fire a normal mms request, verify the factory will only see the request. + final TestNetworkCallback mmsNetworkCallback = new TestNetworkCallback(); + final NetworkRequest mmsRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_MMS).build(); + mCm.requestNetwork(mmsRequest, mmsNetworkCallback); + testFactory.expectRequestAdd(); + testFactory.assertRequestCountEquals(2); + assertTrue(testFactory.getMyStartRequested()); + + // Unregister best matching callback, verify factory see no change. + mCm.unregisterNetworkCallback(bestMatchingCb); + expectNoRequestChanged(testFactory); + testFactory.assertRequestCountEquals(2); + assertTrue(testFactory.getMyStartRequested()); + } finally { + testFactory.terminate(); + } + } + + @Test + public void testRegisterBestMatchingNetworkCallback_trackBestNetwork() throws Exception { + final TestNetworkCallback bestMatchingCb = new TestNetworkCallback(); + mCm.registerBestMatchingNetworkCallback( + new NetworkRequest.Builder().addCapability(NET_CAPABILITY_TRUSTED).build(), + bestMatchingCb, mCsHandlerThread.getThreadHandler()); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + bestMatchingCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + bestMatchingCb.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + + // Change something on cellular to trigger capabilities changed, since the callback + // only cares about the best network, verify it received nothing from cellular. + mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); + bestMatchingCb.assertNoCallback(); + + // Make cellular the best network again, verify the callback now tracks cellular. + mWiFiNetworkAgent.adjustScore(-50); + bestMatchingCb.expectAvailableCallbacksValidated(mCellNetworkAgent); + + // Make cellular temporary non-trusted, which will not satisfying the request. + // Verify the callback switch from/to the other network accordingly. + mCellNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED); + bestMatchingCb.expectAvailableCallbacksValidated(mWiFiNetworkAgent); + mCellNetworkAgent.addCapability(NET_CAPABILITY_TRUSTED); + bestMatchingCb.expectAvailableDoubleValidatedCallbacks(mCellNetworkAgent); + + // Verify the callback doesn't care about wifi disconnect. + mWiFiNetworkAgent.disconnect(); + bestMatchingCb.assertNoCallback(); + mCellNetworkAgent.disconnect(); + bestMatchingCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + } + + private UidRangeParcel[] uidRangeFor(final UserHandle handle) { + UidRange range = UidRange.createForUser(handle); + return new UidRangeParcel[] { new UidRangeParcel(range.start, range.stop) }; + } + + private static class TestOnCompleteListener implements Runnable { + final class OnComplete {} + final ArrayTrackRecord<OnComplete>.ReadHead mHistory = + new ArrayTrackRecord<OnComplete>().newReadHead(); + + @Override + public void run() { + mHistory.add(new OnComplete()); + } + + public void expectOnComplete() { + assertNotNull(mHistory.poll(TIMEOUT_MS, it -> true)); + } + } + + private TestNetworkAgentWrapper makeEnterpriseNetworkAgent() throws Exception { + final NetworkCapabilities workNc = new NetworkCapabilities(); + workNc.addCapability(NET_CAPABILITY_ENTERPRISE); + workNc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + return new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), workNc); + } + + private TestNetworkCallback mEnterpriseCallback; + private UserHandle setupEnterpriseNetwork() { + final UserHandle userHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID); + mServiceContext.setWorkProfile(userHandle, true); + + // File a request to avoid the enterprise network being disconnected as soon as the default + // request goes away – it would make impossible to test that networkRemoveUidRanges + // is called, as the network would disconnect first for lack of a request. + mEnterpriseCallback = new TestNetworkCallback(); + final NetworkRequest keepUpRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_ENTERPRISE) + .build(); + mCm.requestNetwork(keepUpRequest, mEnterpriseCallback); + return userHandle; + } + + private void maybeTearDownEnterpriseNetwork() { + if (null != mEnterpriseCallback) { + mCm.unregisterNetworkCallback(mEnterpriseCallback); + } + } + + /** + * Make sure per-profile networking preference behaves as expected when the enterprise network + * goes up and down while the preference is active. Make sure they behave as expected whether + * there is a general default network or not. + */ + @Test + public void testPreferenceForUserNetworkUpDown() throws Exception { + final InOrder inOrder = inOrder(mMockNetd); + final UserHandle testHandle = setupEnterpriseNetwork(); + registerDefaultNetworkCallbacks(); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId, + INetd.PERMISSION_NONE); + + final TestOnCompleteListener listener = new TestOnCompleteListener(); + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + + // Setting a network preference for this user will create a new set of routing rules for + // the UID range that corresponds to this user, so as to define the default network + // for these apps separately. This is true because the multi-layer request relevant to + // this UID range contains a TRACK_DEFAULT, so the range will be moved through UID-specific + // rules to the correct network – in this case the system default network. The case where + // the default network for the profile happens to be the same as the system default + // is not handled specially, the rules are always active as long as a preference is set. + inOrder.verify(mMockNetd).networkAddUidRanges(mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle)); + + // The enterprise network is not ready yet. + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, + mProfileDefaultNetworkCallback); + + final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent(); + workAgent.connect(false); + + mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent); + mSystemDefaultNetworkCallback.assertNoCallback(); + mDefaultNetworkCallback.assertNoCallback(); + inOrder.verify(mMockNetd).networkCreatePhysical(workAgent.getNetwork().netId, + INetd.PERMISSION_SYSTEM); + inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle)); + inOrder.verify(mMockNetd).networkRemoveUidRanges(mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle)); + + // Make sure changes to the work agent send callbacks to the app in the work profile, but + // not to the other apps. + workAgent.setNetworkValid(true /* isStrictMode */); + workAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); + mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, + nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED) + && nc.hasCapability(NET_CAPABILITY_ENTERPRISE)); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + + workAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); + mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, nc -> + nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + + // Conversely, change a capability on the system-wide default network and make sure + // that only the apps outside of the work profile receive the callbacks. + mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); + mSystemDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc -> + nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); + mDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc -> + nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); + mProfileDefaultNetworkCallback.assertNoCallback(); + + // Disconnect and reconnect the system-wide default network and make sure that the + // apps on this network see the appropriate callbacks, and the app on the work profile + // doesn't because it continues to use the enterprise network. + mCellNetworkAgent.disconnect(); + mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + mProfileDefaultNetworkCallback.assertNoCallback(); + waitForIdle(); + inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mProfileDefaultNetworkCallback.assertNoCallback(); + inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId, + INetd.PERMISSION_NONE); + + // When the agent disconnects, test that the app on the work profile falls back to the + // default network. + workAgent.disconnect(); + mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent); + mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + inOrder.verify(mMockNetd).networkAddUidRanges(mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle)); + waitForIdle(); + inOrder.verify(mMockNetd).networkDestroy(workAgent.getNetwork().netId); + + mCellNetworkAgent.disconnect(); + mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + + waitForIdle(); + inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId); + + // If the control comes here, callbacks seem to behave correctly in the presence of + // a default network when the enterprise network goes up and down. Now, make sure they + // also behave correctly in the absence of a system-wide default network. + final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(); + workAgent2.connect(false); + + mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent2); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + inOrder.verify(mMockNetd).networkCreatePhysical(workAgent2.getNetwork().netId, + INetd.PERMISSION_SYSTEM); + inOrder.verify(mMockNetd).networkAddUidRanges(workAgent2.getNetwork().netId, + uidRangeFor(testHandle)); + + workAgent2.setNetworkValid(true /* isStrictMode */); + workAgent2.mNetworkMonitor.forceReevaluation(Process.myUid()); + mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent2, + nc -> nc.hasCapability(NET_CAPABILITY_ENTERPRISE) + && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + inOrder.verify(mMockNetd, never()).networkAddUidRanges(anyInt(), any()); + + // When the agent disconnects, test that the app on the work profile falls back to the + // default network. + workAgent2.disconnect(); + mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent2); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + waitForIdle(); + inOrder.verify(mMockNetd).networkDestroy(workAgent2.getNetwork().netId); + + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, + mProfileDefaultNetworkCallback); + + // Callbacks will be unregistered by tearDown() + } + + /** + * Test that, in a given networking context, calling setPreferenceForUser to set per-profile + * defaults on then off works as expected. + */ + @Test + public void testSetPreferenceForUserOnOff() throws Exception { + final InOrder inOrder = inOrder(mMockNetd); + final UserHandle testHandle = setupEnterpriseNetwork(); + + // Connect both a regular cell agent and an enterprise network first. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent(); + workAgent.connect(true); + + final TestOnCompleteListener listener = new TestOnCompleteListener(); + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId, + INetd.PERMISSION_NONE); + inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle)); + + registerDefaultNetworkCallbacks(); + + mSystemDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent); + + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_DEFAULT, + r -> r.run(), listener); + listener.expectOnComplete(); + + mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + inOrder.verify(mMockNetd).networkRemoveUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle)); + + workAgent.disconnect(); + mCellNetworkAgent.disconnect(); + + // Callbacks will be unregistered by tearDown() + } + + /** + * Test per-profile default networks for two different profiles concurrently. + */ + @Test + public void testSetPreferenceForTwoProfiles() throws Exception { + final InOrder inOrder = inOrder(mMockNetd); + final UserHandle testHandle2 = setupEnterpriseNetwork(); + final UserHandle testHandle4 = UserHandle.of(TEST_WORK_PROFILE_USER_ID + 2); + mServiceContext.setWorkProfile(testHandle4, true); + registerDefaultNetworkCallbacks(); + + final TestNetworkCallback app4Cb = new TestNetworkCallback(); + final int testWorkProfileAppUid4 = + UserHandle.getUid(testHandle4.getIdentifier(), TEST_APP_ID); + registerDefaultNetworkCallbackAsUid(app4Cb, testWorkProfileAppUid4); + + // Connect both a regular cell agent and an enterprise network first. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent(); + workAgent.connect(true); + + mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + app4Cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId, + INetd.PERMISSION_NONE); + inOrder.verify(mMockNetd).networkCreatePhysical(workAgent.getNetwork().netId, + INetd.PERMISSION_SYSTEM); + + final TestOnCompleteListener listener = new TestOnCompleteListener(); + mCm.setProfileNetworkPreference(testHandle2, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle2)); + + mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, + app4Cb); + + mCm.setProfileNetworkPreference(testHandle4, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle4)); + + app4Cb.expectAvailableCallbacksValidated(workAgent); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, + mProfileDefaultNetworkCallback); + + mCm.setProfileNetworkPreference(testHandle2, PROFILE_NETWORK_PREFERENCE_DEFAULT, + r -> r.run(), listener); + listener.expectOnComplete(); + inOrder.verify(mMockNetd).networkRemoveUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle2)); + + mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, + app4Cb); + + workAgent.disconnect(); + mCellNetworkAgent.disconnect(); + + mCm.unregisterNetworkCallback(app4Cb); + // Other callbacks will be unregistered by tearDown() + } + + @Test + public void testProfilePreferenceRemovedUponUserRemoved() throws Exception { + final InOrder inOrder = inOrder(mMockNetd); + final UserHandle testHandle = setupEnterpriseNetwork(); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + final TestOnCompleteListener listener = new TestOnCompleteListener(); + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId, + INetd.PERMISSION_NONE); + inOrder.verify(mMockNetd).networkAddUidRanges(mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle)); + + final Intent removedIntent = new Intent(ACTION_USER_REMOVED); + removedIntent.putExtra(Intent.EXTRA_USER, testHandle); + processBroadcast(removedIntent); + + inOrder.verify(mMockNetd).networkRemoveUidRanges(mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle)); + } + + /** + * Make sure that OEM preference and per-profile preference can't be used at the same + * time and throw ISE if tried + */ + @Test + public void testOemPreferenceAndProfilePreferenceExclusive() throws Exception { + final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID); + mServiceContext.setWorkProfile(testHandle, true); + final TestOnCompleteListener listener = new TestOnCompleteListener(); + + setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest( + OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY); + assertThrows("Should not be able to set per-profile pref while OEM prefs present", + IllegalStateException.class, () -> + mCm.setProfileNetworkPreference(testHandle, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener)); + + // Empty the OEM prefs + final TestOemListenerCallback oemPrefListener = new TestOemListenerCallback(); + final OemNetworkPreferences emptyOemPref = new OemNetworkPreferences.Builder().build(); + mService.setOemNetworkPreference(emptyOemPref, oemPrefListener); + oemPrefListener.expectOnComplete(); + + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + assertThrows("Should not be able to set OEM prefs while per-profile pref is on", + IllegalStateException.class , () -> + mService.setOemNetworkPreference(emptyOemPref, oemPrefListener)); + } + + /** + * Make sure wrong preferences for per-profile default networking are rejected. + */ + @Test + public void testProfileNetworkPrefWrongPreference() throws Exception { + final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID); + mServiceContext.setWorkProfile(testHandle, true); + assertThrows("Should not be able to set an illegal preference", + IllegalArgumentException.class, + () -> mCm.setProfileNetworkPreference(testHandle, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE + 1, null, null)); + } + + /** + * Make sure requests for per-profile default networking for a non-work profile are + * rejected + */ + @Test + public void testProfileNetworkPrefWrongProfile() throws Exception { + final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID); + mServiceContext.setWorkProfile(testHandle, false); + assertThrows("Should not be able to set a user pref for a non-work profile", + IllegalArgumentException.class , () -> + mCm.setProfileNetworkPreference(testHandle, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE, null, null)); } } diff --git a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt index a10a3c81bc86..5ec111954fcc 100644 --- a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt +++ b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt @@ -21,13 +21,29 @@ package com.android.server +import android.content.Context +import android.content.pm.PackageManager +import android.content.pm.PackageManager.FEATURE_WIFI +import android.content.pm.PackageManager.FEATURE_WIFI_DIRECT import android.net.ConnectivityManager.TYPE_ETHERNET import android.net.ConnectivityManager.TYPE_MOBILE +import android.net.ConnectivityManager.TYPE_MOBILE_CBS +import android.net.ConnectivityManager.TYPE_MOBILE_DUN +import android.net.ConnectivityManager.TYPE_MOBILE_EMERGENCY +import android.net.ConnectivityManager.TYPE_MOBILE_FOTA +import android.net.ConnectivityManager.TYPE_MOBILE_HIPRI +import android.net.ConnectivityManager.TYPE_MOBILE_IA +import android.net.ConnectivityManager.TYPE_MOBILE_IMS +import android.net.ConnectivityManager.TYPE_MOBILE_MMS import android.net.ConnectivityManager.TYPE_MOBILE_SUPL +import android.net.ConnectivityManager.TYPE_VPN import android.net.ConnectivityManager.TYPE_WIFI +import android.net.ConnectivityManager.TYPE_WIFI_P2P import android.net.ConnectivityManager.TYPE_WIMAX +import android.net.EthernetManager import android.net.NetworkInfo.DetailedState.CONNECTED import android.net.NetworkInfo.DetailedState.DISCONNECTED +import android.telephony.TelephonyManager import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 import com.android.server.ConnectivityService.LegacyTypeTracker @@ -36,7 +52,6 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertNull import org.junit.Assert.assertSame import org.junit.Assert.assertTrue -import org.junit.Assert.fail import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any @@ -52,88 +67,130 @@ const val UNSUPPORTED_TYPE = TYPE_WIMAX @RunWith(AndroidJUnit4::class) @SmallTest class LegacyTypeTrackerTest { - private val supportedTypes = arrayOf(TYPE_MOBILE, TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_SUPL) + private val supportedTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_MOBILE, + TYPE_MOBILE_SUPL, TYPE_MOBILE_MMS, TYPE_MOBILE_SUPL, TYPE_MOBILE_DUN, TYPE_MOBILE_HIPRI, + TYPE_MOBILE_FOTA, TYPE_MOBILE_IMS, TYPE_MOBILE_CBS, TYPE_MOBILE_IA, + TYPE_MOBILE_EMERGENCY, TYPE_VPN) private val mMockService = mock(ConnectivityService::class.java).apply { doReturn(false).`when`(this).isDefaultNetwork(any()) } - private val mTracker = LegacyTypeTracker(mMockService).apply { - supportedTypes.forEach { - addSupportedType(it) - } + private val mPm = mock(PackageManager::class.java) + private val mContext = mock(Context::class.java).apply { + doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI) + doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI_DIRECT) + doReturn(mPm).`when`(this).packageManager + doReturn(mock(EthernetManager::class.java)).`when`(this).getSystemService( + Context.ETHERNET_SERVICE) + } + private val mTm = mock(TelephonyManager::class.java).apply { + doReturn(true).`when`(this).isDataCapable + } + + private fun makeTracker() = LegacyTypeTracker(mMockService).apply { + loadSupportedTypes(mContext, mTm) } @Test fun testSupportedTypes() { - try { - mTracker.addSupportedType(supportedTypes[0]) - fail("Expected IllegalStateException") - } catch (expected: IllegalStateException) {} + val tracker = makeTracker() supportedTypes.forEach { - assertTrue(mTracker.isTypeSupported(it)) + assertTrue(tracker.isTypeSupported(it)) + } + assertFalse(tracker.isTypeSupported(UNSUPPORTED_TYPE)) + } + + @Test + fun testSupportedTypes_NoEthernet() { + doReturn(null).`when`(mContext).getSystemService(Context.ETHERNET_SERVICE) + assertFalse(makeTracker().isTypeSupported(TYPE_ETHERNET)) + } + + @Test + fun testSupportedTypes_NoTelephony() { + doReturn(false).`when`(mTm).isDataCapable + val tracker = makeTracker() + val nonMobileTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_VPN) + nonMobileTypes.forEach { + assertTrue(tracker.isTypeSupported(it)) + } + supportedTypes.toSet().minus(nonMobileTypes).forEach { + assertFalse(tracker.isTypeSupported(it)) + } + } + + @Test + fun testSupportedTypes_NoWifiDirect() { + doReturn(false).`when`(mPm).hasSystemFeature(FEATURE_WIFI_DIRECT) + val tracker = makeTracker() + assertFalse(tracker.isTypeSupported(TYPE_WIFI_P2P)) + supportedTypes.toSet().minus(TYPE_WIFI_P2P).forEach { + assertTrue(tracker.isTypeSupported(it)) } - assertFalse(mTracker.isTypeSupported(UNSUPPORTED_TYPE)) } @Test fun testSupl() { + val tracker = makeTracker() val mobileNai = mock(NetworkAgentInfo::class.java) - mTracker.add(TYPE_MOBILE, mobileNai) + tracker.add(TYPE_MOBILE, mobileNai) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE) reset(mMockService) - mTracker.add(TYPE_MOBILE_SUPL, mobileNai) + tracker.add(TYPE_MOBILE_SUPL, mobileNai) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL) reset(mMockService) - mTracker.remove(TYPE_MOBILE_SUPL, mobileNai, false /* wasDefault */) + tracker.remove(TYPE_MOBILE_SUPL, mobileNai, false /* wasDefault */) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL) reset(mMockService) - mTracker.add(TYPE_MOBILE_SUPL, mobileNai) + tracker.add(TYPE_MOBILE_SUPL, mobileNai) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL) reset(mMockService) - mTracker.remove(mobileNai, false) + tracker.remove(mobileNai, false) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE) } @Test fun testAddNetwork() { + val tracker = makeTracker() val mobileNai = mock(NetworkAgentInfo::class.java) val wifiNai = mock(NetworkAgentInfo::class.java) - mTracker.add(TYPE_MOBILE, mobileNai) - mTracker.add(TYPE_WIFI, wifiNai) - assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai) - assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai) + tracker.add(TYPE_MOBILE, mobileNai) + tracker.add(TYPE_WIFI, wifiNai) + assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai) + assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai) // Make sure adding a second NAI does not change the results. val secondMobileNai = mock(NetworkAgentInfo::class.java) - mTracker.add(TYPE_MOBILE, secondMobileNai) - assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai) - assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai) + tracker.add(TYPE_MOBILE, secondMobileNai) + assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai) + assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai) // Make sure removing a network that wasn't added for this type is a no-op. - mTracker.remove(TYPE_MOBILE, wifiNai, false /* wasDefault */) - assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai) - assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai) + tracker.remove(TYPE_MOBILE, wifiNai, false /* wasDefault */) + assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai) + assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai) // Remove the top network for mobile and make sure the second one becomes the network // of record for this type. - mTracker.remove(TYPE_MOBILE, mobileNai, false /* wasDefault */) - assertSame(mTracker.getNetworkForType(TYPE_MOBILE), secondMobileNai) - assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai) + tracker.remove(TYPE_MOBILE, mobileNai, false /* wasDefault */) + assertSame(tracker.getNetworkForType(TYPE_MOBILE), secondMobileNai) + assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai) // Make sure adding a network for an unsupported type does not register it. - mTracker.add(UNSUPPORTED_TYPE, mobileNai) - assertNull(mTracker.getNetworkForType(UNSUPPORTED_TYPE)) + tracker.add(UNSUPPORTED_TYPE, mobileNai) + assertNull(tracker.getNetworkForType(UNSUPPORTED_TYPE)) } @Test fun testBroadcastOnDisconnect() { + val tracker = makeTracker() val mobileNai1 = mock(NetworkAgentInfo::class.java) val mobileNai2 = mock(NetworkAgentInfo::class.java) doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai1) - mTracker.add(TYPE_MOBILE, mobileNai1) + tracker.add(TYPE_MOBILE, mobileNai1) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, CONNECTED, TYPE_MOBILE) reset(mMockService) doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai2) - mTracker.add(TYPE_MOBILE, mobileNai2) + tracker.add(TYPE_MOBILE, mobileNai2) verify(mMockService, never()).sendLegacyNetworkBroadcast(any(), any(), anyInt()) - mTracker.remove(TYPE_MOBILE, mobileNai1, false /* wasDefault */) + tracker.remove(TYPE_MOBILE, mobileNai1, false /* wasDefault */) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, DISCONNECTED, TYPE_MOBILE) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai2, CONNECTED, TYPE_MOBILE) } diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java index 1c0ba4f8d8f5..ea2b362c537a 100644 --- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java @@ -40,6 +40,7 @@ import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkProvider; +import android.net.NetworkScore; import android.os.Binder; import android.text.format.DateUtils; @@ -355,8 +356,9 @@ public class LingerMonitorTest { caps.addCapability(0); caps.addTransportType(transport); NetworkAgentInfo nai = new NetworkAgentInfo(null, new Network(netId), info, - new LinkProperties(), caps, 50, mCtx, null, new NetworkAgentConfig() /* config */, - mConnService, mNetd, mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(), + new LinkProperties(), caps, new NetworkScore.Builder().setLegacyInt(50).build(), + mCtx, null, new NetworkAgentConfig() /* config */, mConnService, mNetd, + mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(), mQosCallbackTracker, new ConnectivityService.Dependencies()); nai.everValidated = true; return nai; diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index b8f7fbca3983..11fcea60d98d 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -1026,7 +1026,11 @@ public class VpnTest { .thenReturn(new Network[] { new Network(101) }); when(mConnectivityManager.registerNetworkAgent(any(), any(), any(), any(), - anyInt(), any(), anyInt())).thenReturn(new Network(102)); + any(), any(), anyInt())).thenAnswer(invocation -> { + // The runner has registered an agent and is now ready. + legacyRunnerReady.open(); + return new Network(102); + }); final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), profile); final TestDeps deps = (TestDeps) vpn.mDeps; try { @@ -1048,7 +1052,7 @@ public class VpnTest { ArgumentCaptor<NetworkCapabilities> ncCaptor = ArgumentCaptor.forClass(NetworkCapabilities.class); verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(), - lpCaptor.capture(), ncCaptor.capture(), anyInt(), any(), anyInt()); + lpCaptor.capture(), ncCaptor.capture(), any(), any(), anyInt()); // In this test the expected address is always v4 so /32. // Note that the interface needs to be specified because RouteInfo objects stored in diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index a02002752c38..814cad4ab448 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -74,7 +74,7 @@ import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.util.LocationPermissionChecker; +import com.android.net.module.util.LocationPermissionChecker; import com.android.server.VcnManagementService.VcnCallback; import com.android.server.VcnManagementService.VcnStatusCallbackInfo; import com.android.server.vcn.TelephonySubscriptionTracker; diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index 0e5f5e43f282..ca6448ca9b8c 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -188,7 +188,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection any(), lpCaptor.capture(), ncCaptor.capture(), - anyInt(), + any(), any(), anyInt()); verify(mIpSecSvc) |